From 8a6bd2debed42ed55b4b21dffc937c031c4b2c87 Mon Sep 17 00:00:00 2001 From: nidhiii128 Date: Sun, 7 Jun 2026 11:06:24 +0000 Subject: [PATCH] FINERACT-2493: New command processing - execute job --- .../service/CommandWrapperBuilder.java | 9 --- .../jobs/data/JobParameterDTO.java | 2 + .../fineract/test/service/JobService.java | 13 +--- .../suite/JobSuiteInitializerStep.java | 4 +- .../core/config/SecurityConfig.java | 7 ++ .../jobs/api/SchedulerJobApiResource.java | 73 ++++++++----------- .../api/SchedulerJobApiResourceSwagger.java | 6 +- .../jobs/command/JobExecuteCommand.java | 29 ++++++++ .../jobs/data/JobExecuteRequest.java | 44 +++++++++++ .../jobs/data/JobExecuteResponse.java | 39 ++++++++++ .../handler/ExecuteJobCommandHandler.java | 46 ------------ .../handler/JobExecuteCommandHandler.java | 59 +++++++++++++++ .../jobs/service/JobRegisterService.java | 6 +- .../jobs/service/JobRegisterServiceImpl.java | 72 +++++++----------- .../src/main/resources/application.properties | 7 ++ .../handler/ExecuteJobCommandHandlerTest.java | 64 ---------------- .../handler/JobExecuteCommandHandlerTest.java | 71 ++++++++++++++++++ .../InstanceModeIntegrationTest.java | 9 ++- .../feign/helpers/FeignSchedulerHelper.java | 46 +++++------- .../common/SchedulerJobHelper.java | 3 +- 20 files changed, 354 insertions(+), 255 deletions(-) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/command/JobExecuteCommand.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobExecuteRequest.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobExecuteResponse.java delete mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/handler/ExecuteJobCommandHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/handler/JobExecuteCommandHandler.java delete mode 100644 fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/handler/ExecuteJobCommandHandlerTest.java create mode 100644 fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/handler/JobExecuteCommandHandlerTest.java diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index f4a0dc6e19d..1d2b5fa1ab4 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -79,7 +79,6 @@ import static org.apache.fineract.commands.domain.CommandWrapperConstants.ACTION_DOWNPAYMENT; import static org.apache.fineract.commands.domain.CommandWrapperConstants.ACTION_ENABLE; import static org.apache.fineract.commands.domain.CommandWrapperConstants.ACTION_EXECUTE; -import static org.apache.fineract.commands.domain.CommandWrapperConstants.ACTION_EXECUTEJOB; import static org.apache.fineract.commands.domain.CommandWrapperConstants.ACTION_FORCE_WITHDRAWAL; import static org.apache.fineract.commands.domain.CommandWrapperConstants.ACTION_FORECLOSURE; import static org.apache.fineract.commands.domain.CommandWrapperConstants.ACTION_GET; @@ -2565,14 +2564,6 @@ public CommandWrapperBuilder updateJobDetail(final Long jobId) { return this; } - public CommandWrapperBuilder executeSchedulerJob(final Long jobId) { - this.actionName = ACTION_EXECUTEJOB; - this.entityName = ENTITY_SCHEDULER; - this.entityId = jobId; - this.href = "/jobs/" + jobId + "?command=executeJob"; - return this; - } - /** * Deposit account mappings */ diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobParameterDTO.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobParameterDTO.java index 59a24df70af..42751d68941 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobParameterDTO.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobParameterDTO.java @@ -21,9 +21,11 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; @Data @AllArgsConstructor +@NoArgsConstructor @EqualsAndHashCode public class JobParameterDTO { diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/service/JobService.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/service/JobService.java index dcc73a346d6..aa6b7d23732 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/service/JobService.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/service/JobService.java @@ -29,13 +29,12 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.client.feign.FineractFeignClient; -import org.apache.fineract.client.models.ExecuteJobRequest; import org.apache.fineract.client.models.GetJobsJobIDJobRunHistoryResponse; import org.apache.fineract.client.models.JobDetailHistoryDataSwagger; +import org.apache.fineract.client.models.JobExecuteRequest; import org.apache.fineract.test.data.job.Job; import org.apache.fineract.test.data.job.JobResolver; import org.apache.fineract.test.messaging.config.JobPollingProperties; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component @@ -43,12 +42,8 @@ @Slf4j public class JobService { - @Autowired - private FineractFeignClient fineractClient; - - @Autowired - private JobPollingProperties jobPollingProperties; - + private final FineractFeignClient fineractClient; + private final JobPollingProperties jobPollingProperties; private final JobResolver jobResolver; public void execute(Job job) { @@ -59,7 +54,7 @@ public void execute(Job job) { private void execute(Long jobId) { Map queryParams = new HashMap<>(); queryParams.put("command", "executeJob"); - executeVoid(() -> fineractClient.schedulerJob().executeJob(jobId, new ExecuteJobRequest(), queryParams)); + executeVoid(() -> fineractClient.schedulerJob().executeJob(jobId, new JobExecuteRequest(), queryParams)); } public void executeAndWait(Job job) { diff --git a/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/suite/JobSuiteInitializerStep.java b/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/suite/JobSuiteInitializerStep.java index 7ffaea59239..f23d4980f1f 100644 --- a/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/suite/JobSuiteInitializerStep.java +++ b/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/suite/JobSuiteInitializerStep.java @@ -24,8 +24,8 @@ import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.client.feign.FineractFeignClient; -import org.apache.fineract.client.models.ExecuteJobRequest; import org.apache.fineract.client.models.GetJobsResponse; +import org.apache.fineract.client.models.JobExecuteRequest; import org.apache.fineract.client.models.PutJobsJobIDRequest; import org.springframework.stereotype.Component; @@ -65,7 +65,7 @@ private void enableAndExecuteEventJob() throws InterruptedException { // Manually execute once immediately to publish any queued events from initialization log.debug("Manually executing '{}' job once to publish queued events...", SEND_ASYNCHRONOUS_EVENTS_JOB_NAME); - executeVoid(() -> fineractClient.schedulerJob().executeJob(jobId, new ExecuteJobRequest(), Map.of("command", "executeJob"))); + executeVoid(() -> fineractClient.schedulerJob().executeJob(jobId, new JobExecuteRequest(), Map.of("command", "executeJob"))); // Poll job history to confirm it ran log.debug("Polling job history to confirm initial execution..."); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java index 27a328d7b8c..4cc81014560 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java @@ -397,6 +397,13 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_HOOK") .requestMatchers(API_MATCHER.matcher(HttpMethod.DELETE, "/api/*/hooks/*")) .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "DELETE_HOOK") + + // execute scheduler job (by id) + .requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/jobs/*")) + .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "EXECUTEJOB_SCHEDULER") + .requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/jobs/short-name/*")) + .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "EXECUTEJOB_SCHEDULER") + // template .requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/templates/*")) .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_TEMPLATE") diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResource.java index 79df8a3d421..3b6158c6e01 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResource.java @@ -49,6 +49,7 @@ import java.util.Objects; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; +import org.apache.fineract.command.core.CommandDispatcher; import org.apache.fineract.commands.domain.CommandWrapper; import org.apache.fineract.commands.service.CommandWrapperBuilder; import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; @@ -62,11 +63,13 @@ import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer; import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.SearchParameters; +import org.apache.fineract.infrastructure.jobs.command.JobExecuteCommand; import org.apache.fineract.infrastructure.jobs.data.JobDetailData; import org.apache.fineract.infrastructure.jobs.data.JobDetailHistoryData; +import org.apache.fineract.infrastructure.jobs.data.JobExecuteRequest; +import org.apache.fineract.infrastructure.jobs.data.JobExecuteResponse; import org.apache.fineract.infrastructure.jobs.service.JobRegisterService; import org.apache.fineract.infrastructure.jobs.service.SchedulerJobRunnerReadService; -import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.SqlValidator; import org.springframework.stereotype.Component; @@ -87,6 +90,7 @@ public class SchedulerJobApiResource { private final PlatformSecurityContext context; private final FineractProperties fineractProperties; private final SqlValidator sqlValidator; + private final CommandDispatcher dispatcher; @GET @Operation(summary = "Retrieve Scheduler Jobs", operationId = "retrieveAllSchedulerJobs", description = "Returns the list of jobs.\n" @@ -151,25 +155,36 @@ public String retrieveHistoryByShortName(@Context final UriInfo uriInfo, @Path("{" + SchedulerJobApiConstants.JOB_ID + "}") @Consumes({ MediaType.APPLICATION_JSON }) @Operation(summary = "Run a Job", description = "Manually Execute Specific Job.") - @RequestBody(content = @Content(schema = @Schema(implementation = SchedulerJobApiResourceSwagger.ExecuteJobRequest.class))) - @ApiResponse(responseCode = "200", description = "POST: jobs/1?command=executeJob") - public Response executeJob(@PathParam(SchedulerJobApiConstants.JOB_ID) @Parameter(description = "jobId") final Long jobId, - @QueryParam(SchedulerJobApiConstants.COMMAND) @Parameter(description = "command") final String commandParam, - @Parameter(hidden = true) final String jsonRequestBody) { - return executeJob(IdTypeResolver.resolveDefault(), Objects.toString(jobId, null), commandParam, jsonRequestBody); + public Response executeJob(@PathParam(SchedulerJobApiConstants.JOB_ID) final Long jobId, + @QueryParam(SchedulerJobApiConstants.COMMAND) final String commandParam, JobExecuteRequest request) { + return dispatchExecuteJob(jobId, commandParam, request); } @POST @Path(SHORT_NAME_PARAM + "/{shortName}") @Consumes({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Run a Job", description = "Manually Execute Specific Job.") - @RequestBody(content = @Content(schema = @Schema(implementation = SchedulerJobApiResourceSwagger.ExecuteJobRequest.class))) - @ApiResponse(responseCode = "200", description = "POST: jobs/short-name/SA_PINT?command=executeJob") - public Response executeJobByShortName( - @PathParam("shortName") @Parameter(required = true, description = SchedulerJobApiConstants.SHORT_NAME_PARAM) final String shortName, - @QueryParam(SchedulerJobApiConstants.COMMAND) @Parameter(description = "command") final String commandParam, - @Parameter(hidden = true) final String jsonRequestBody) { - return executeJob(IdTypeResolver.resolve(SHORT_NAME_PARAM), shortName, commandParam, jsonRequestBody); + @Operation(summary = "Run a Job", description = "Manually Execute Specific Job (by short name).") + public Response executeJobByShortName(@PathParam("shortName") final String shortName, + @QueryParam(SchedulerJobApiConstants.COMMAND) final String commandParam, JobExecuteRequest request) { + final Long jobId = schedulerJobRunnerReadService.retrieveId(IdTypeResolver.IdType.SHORT_NAME, shortName); + return dispatchExecuteJob(jobId, commandParam, request); + } + + private Response dispatchExecuteJob(Long jobId, String commandParam, JobExecuteRequest request) { + if (!fineractProperties.getMode().isBatchManagerEnabled()) { + return Response.status(Status.METHOD_NOT_ALLOWED).entity(ApiGlobalErrorResponse.invalidInstanceTypeMethod("Batch")).build(); + } + if (!is(commandParam, SchedulerJobApiConstants.COMMAND_EXECUTE_JOB)) { + throw new UnrecognizedQueryParamException(SchedulerJobApiConstants.COMMAND, commandParam); + } + if (request == null) { + request = JobExecuteRequest.builder().build(); + } + request.setJobId(jobId); + var command = new JobExecuteCommand(); + command.setPayload(request); + dispatcher.dispatch(command).get(); + return Response.status(202).build(); } @PUT @@ -219,34 +234,6 @@ private String retrieveHistory(@NotNull IdTypeResolver.IdType idType, String ide return jobHistoryToApiJsonSerializer.serialize(settings, jobHistoryData, JOB_HISTORY_RESPONSE_DATA_PARAMETERS); } - private Response executeJob(@NotNull IdTypeResolver.IdType idType, String identifier, String commandParam, String jsonRequestBody) { - // check the logged-in user have permissions to execute scheduler jobs - Response response; - if (fineractProperties.getMode().isBatchManagerEnabled()) { - final boolean hasNotPermission = context.authenticatedUser().hasNotPermissionForAnyOf("ALL_FUNCTIONS", "EXECUTEJOB_SCHEDULER"); - if (hasNotPermission) { - final String authorizationMessage = "User has no authority to execute scheduler jobs"; - throw new NoAuthorizationException(authorizationMessage); - } - response = Response.status(400).build(); - if (is(commandParam, SchedulerJobApiConstants.COMMAND_EXECUTE_JOB)) { - Long jobId = schedulerJobRunnerReadService.retrieveId(idType, identifier); - final CommandWrapper commandRequest = new CommandWrapperBuilder() // - .executeSchedulerJob(jobId) // - .withJson(jsonRequestBody) // - .build(); - commandsSourceWritePlatformService.logCommandSource(commandRequest); - response = Response.status(202).build(); - } else { - throw new UnrecognizedQueryParamException(SchedulerJobApiConstants.COMMAND, commandParam); - } - } else { - ApiGlobalErrorResponse errorResponse = ApiGlobalErrorResponse.invalidInstanceTypeMethod("Batch"); - response = Response.status(Status.METHOD_NOT_ALLOWED).entity(errorResponse).build(); - } - return response; - } - private String updateJobDetail(@NotNull IdTypeResolver.IdType idType, String identifier, String jsonRequestBody) { Long jobId = schedulerJobRunnerReadService.retrieveId(idType, identifier); final CommandWrapper commandRequest = new CommandWrapperBuilder() // diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResourceSwagger.java index 37884423888..94d66b5a691 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResourceSwagger.java @@ -106,10 +106,10 @@ private JobDetailHistoryDataSwagger() {} } - @Schema(description = "ExecuteJobRequest") - public static final class ExecuteJobRequest { + @Schema(description = "JobExecuteRequest") + public static final class JobExecuteRequest { - private ExecuteJobRequest() { + private JobExecuteRequest() { } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/command/JobExecuteCommand.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/command/JobExecuteCommand.java new file mode 100644 index 00000000000..1e2a5ba597c --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/command/JobExecuteCommand.java @@ -0,0 +1,29 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.fineract.infrastructure.jobs.command; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.fineract.command.core.Command; +import org.apache.fineract.infrastructure.jobs.data.JobExecuteRequest; + +@Data +@EqualsAndHashCode(callSuper = true) +public class JobExecuteCommand extends Command {} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobExecuteRequest.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobExecuteRequest.java new file mode 100644 index 00000000000..0c23a59e9cb --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobExecuteRequest.java @@ -0,0 +1,44 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.fineract.infrastructure.jobs.data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldNameConstants; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@FieldNameConstants +public class JobExecuteRequest implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long jobId; + + private List parameters; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobExecuteResponse.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobExecuteResponse.java new file mode 100644 index 00000000000..760167ce169 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobExecuteResponse.java @@ -0,0 +1,39 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.fineract.infrastructure.jobs.data; + +import java.io.Serial; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +public class JobExecuteResponse implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long resourceId; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/handler/ExecuteJobCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/handler/ExecuteJobCommandHandler.java deleted file mode 100644 index 83afa584db2..00000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/handler/ExecuteJobCommandHandler.java +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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.apache.fineract.infrastructure.jobs.handler; - -import lombok.RequiredArgsConstructor; -import org.apache.fineract.commands.annotation.CommandType; -import org.apache.fineract.commands.handler.NewCommandSourceHandler; -import org.apache.fineract.infrastructure.core.api.JsonCommand; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; -import org.apache.fineract.infrastructure.jobs.service.JobRegisterService; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -@CommandType(entity = "SCHEDULER", action = "EXECUTEJOB") -public class ExecuteJobCommandHandler implements NewCommandSourceHandler { - - private final JobRegisterService jobRegisterService; - - @Override - public CommandProcessingResult processCommand(final JsonCommand command) { - final Long jobId = command.entityId(); - jobRegisterService.executeJobWithParameters(jobId, command.json()); - return new CommandProcessingResultBuilder() // - .withCommandId(command.commandId()) // - .withEntityId(jobId) // - .build(); - } -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/handler/JobExecuteCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/handler/JobExecuteCommandHandler.java new file mode 100644 index 00000000000..0c8fc30a298 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/handler/JobExecuteCommandHandler.java @@ -0,0 +1,59 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.fineract.infrastructure.jobs.handler; + +import io.github.resilience4j.retry.annotation.Retry; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.command.core.Command; +import org.apache.fineract.command.core.CommandHandler; +import org.apache.fineract.infrastructure.jobs.data.JobExecuteRequest; +import org.apache.fineract.infrastructure.jobs.data.JobExecuteResponse; +import org.apache.fineract.infrastructure.jobs.data.JobParameterDTO; +import org.apache.fineract.infrastructure.jobs.service.JobRegisterService; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Component +@RequiredArgsConstructor +public class JobExecuteCommandHandler implements CommandHandler { + + private final JobRegisterService jobRegisterService; + + @Retry(name = "commandJobExecute", fallbackMethod = "fallback") + @Override + @Transactional + public JobExecuteResponse handle(Command command) { + JobExecuteRequest req = command.getPayload(); + List params = req.getParameters(); + Set paramSet = (params != null && !params.isEmpty()) ? new HashSet<>(params) : null; + jobRegisterService.executeJobWithParameters(req.getJobId(), paramSet); + return JobExecuteResponse.builder().resourceId(req.getJobId()).build(); + } + + @Override + public JobExecuteResponse fallback(Command command, Throwable t) { + return CommandHandler.super.fallback(command, t); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterService.java index 71810c7146b..00a65ac94d6 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterService.java @@ -18,12 +18,12 @@ */ package org.apache.fineract.infrastructure.jobs.service; +import java.util.Set; +import org.apache.fineract.infrastructure.jobs.data.JobParameterDTO; import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail; public interface JobRegisterService { - void executeJobWithParameters(Long jobId, String jobParametersJson); - void rescheduleJob(Long jobId); void pauseScheduler(); @@ -38,4 +38,6 @@ public interface JobRegisterService { void stopAllSchedulers(); + void executeJobWithParameters(Long jobId, Set parameters); + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java index a832c7311be..8997e88958e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java @@ -27,6 +27,7 @@ import java.util.Properties; import java.util.Set; import java.util.TimeZone; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; @@ -51,7 +52,6 @@ import org.springframework.batch.core.Job; import org.springframework.batch.core.configuration.JobLocator; import org.springframework.batch.core.launch.NoSuchJobException; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextClosedEvent; import org.springframework.scheduling.quartz.CronTriggerFactoryBean; @@ -65,37 +65,37 @@ */ @Service @Slf4j +@RequiredArgsConstructor public class JobRegisterServiceImpl implements JobRegisterService, ApplicationListener { - private static final String JOB_EXECUTION_FAILED_MESSAGE = "Job execution failed for job with name: "; - - @Autowired - private SchedularWritePlatformService schedularWritePlatformService; - - @Autowired - private SchedulerJobListener schedulerJobListener; - - @Autowired - private SchedulerTriggerListener globalSchedulerTriggerListener; - private static final HashMap SCHEDULERS = new HashMap<>(4); + private static final String JOB_STARTER_METHOD_NAME = "run"; - @Autowired - private FineractProperties fineractProperties; - - @Autowired - private JobLocator jobLocator; - - @Autowired - private JobStarter jobStarter; - - @Autowired - private JobParameterDataParser dataParser; - - @Autowired - private JobNameService jobNameService; + private final SchedularWritePlatformService schedularWritePlatformService; + private final SchedulerJobListener schedulerJobListener; + private final SchedulerTriggerListener globalSchedulerTriggerListener; + private final FineractProperties fineractProperties; + private final JobLocator jobLocator; + private final JobStarter jobStarter; + private final JobNameService jobNameService; - private static final String JOB_STARTER_METHOD_NAME = "run"; + @Override + public void executeJobWithParameters(final Long jobId, Set jobParameterDTOSet) { + // after jobs - pr1 gets merged + // final ScheduledJobDetail scheduledJobDetail = this.schedulerJobRunnerReadService.findByJobId(jobId); + final ScheduledJobDetail scheduledJobDetail = this.schedularWritePlatformService.findByJobId(jobId); + if (scheduledJobDetail == null) { + throw new JobNotFoundException(String.valueOf(jobId)); + } + final String nodeIdStored = scheduledJobDetail.getNodeId().toString(); + if (nodeIdStored.equals(fineractProperties.getNodeId()) || nodeIdStored.equals("0")) { + executeJob(scheduledJobDetail, null, jobParameterDTOSet); + } else { + scheduledJobDetail.setMismatchedJob(true); + this.schedularWritePlatformService.saveOrUpdate(scheduledJobDetail); + throw new JobNodeIdMismatchingException(nodeIdStored, fineractProperties.getNodeId()); + } + } @SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT") public void executeJob(final ScheduledJobDetail scheduledJobDetail, String triggerType, Set jobParameterDTOSet) { @@ -213,24 +213,6 @@ public void rescheduleJob(final Long jobId) { } } - @Override - public void executeJobWithParameters(final Long jobId, String jobParametersJson) { - Set jobParameterDTOSet = dataParser.parseExecution(jobParametersJson); - final ScheduledJobDetail scheduledJobDetail = this.schedularWritePlatformService.findByJobId(jobId); - if (scheduledJobDetail == null) { - throw new JobNotFoundException(String.valueOf(jobId)); - } - final String nodeIdStored = scheduledJobDetail.getNodeId().toString(); - - if (nodeIdStored.equals(fineractProperties.getNodeId()) || nodeIdStored.equals("0")) { - executeJob(scheduledJobDetail, null, jobParameterDTOSet); - } else { - scheduledJobDetail.setMismatchedJob(true); - this.schedularWritePlatformService.saveOrUpdate(scheduledJobDetail); - throw new JobNodeIdMismatchingException(nodeIdStored, fineractProperties.getNodeId()); - } - } - @Override public boolean isSchedulerRunning() { return !this.schedularWritePlatformService.retriveSchedulerDetail().isSuspended(); diff --git a/fineract-provider/src/main/resources/application.properties b/fineract-provider/src/main/resources/application.properties index 230310206f8..dc153fca2b4 100644 --- a/fineract-provider/src/main/resources/application.properties +++ b/fineract-provider/src/main/resources/application.properties @@ -835,6 +835,13 @@ resilience4j.retry.instances.commandStore.enable-exponential-backoff=${FINERACT_ resilience4j.retry.instances.commandStore.exponential-backoff-multiplier=${FINERACT_COMMAND_STORE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2} resilience4j.retry.instances.commandStore.retryExceptions=${FINERACT_COMMAND_STORE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException} +# job execute +resilience4j.retry.instances.commandJobExecute.max-attempts=${FINERACT_COMMAND_JOB_EXECUTE_RETRY_MAX_ATTEMPTS:3} +resilience4j.retry.instances.commandJobExecute.wait-duration=${FINERACT_COMMAND_JOB_EXECUTE_RETRY_WAIT_DURATION:1s} +resilience4j.retry.instances.commandJobExecute.enable-exponential-backoff=${FINERACT_COMMAND_JOB_EXECUTE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true} +resilience4j.retry.instances.commandJobExecute.exponential-backoff-multiplier=${FINERACT_COMMAND_JOB_EXECUTE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2} +resilience4j.retry.instances.commandJobExecute.retryExceptions=${FINERACT_COMMAND_JOB_EXECUTE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException} + # command async (WIP) # fineract.command.async.enabled=${FINERACT_COMMAND_ASYNC_ENABLED:false} diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/handler/ExecuteJobCommandHandlerTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/handler/ExecuteJobCommandHandlerTest.java deleted file mode 100644 index 1120dc084a8..00000000000 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/handler/ExecuteJobCommandHandlerTest.java +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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.apache.fineract.infrastructure.jobs.handler; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import org.apache.fineract.infrastructure.core.api.JsonCommand; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.infrastructure.jobs.service.JobRegisterService; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -class ExecuteJobCommandHandlerTest { - - @Mock - private JobRegisterService jobRegisterService; - - @Mock - private JsonCommand command; - - @InjectMocks - private ExecuteJobCommandHandler underTest; - - @Test - void shouldExecuteJobAndReturnCommandResult() { - // given - Long jobId = 123L; - Long commandId = 456L; - String json = "{\"includeTasks\":true}"; - when(command.entityId()).thenReturn(jobId); - when(command.commandId()).thenReturn(commandId); - when(command.json()).thenReturn(json); - - // when - CommandProcessingResult result = underTest.processCommand(command); - - // then - verify(jobRegisterService).executeJobWithParameters(jobId, json); - assertEquals(commandId, result.getCommandId()); - assertEquals(jobId, result.getResourceId()); - } -} diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/handler/JobExecuteCommandHandlerTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/handler/JobExecuteCommandHandlerTest.java new file mode 100644 index 00000000000..3316205b2ff --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/handler/JobExecuteCommandHandlerTest.java @@ -0,0 +1,71 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.fineract.infrastructure.jobs.handler; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import java.util.Set; +import org.apache.fineract.infrastructure.jobs.command.JobExecuteCommand; +import org.apache.fineract.infrastructure.jobs.data.JobExecuteRequest; +import org.apache.fineract.infrastructure.jobs.data.JobExecuteResponse; +import org.apache.fineract.infrastructure.jobs.service.JobRegisterService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class JobExecuteCommandHandlerTest { + + @Mock + private JobRegisterService jobRegisterService; + + @InjectMocks + private JobExecuteCommandHandler underTest; + + @Test + void handle_withNullParameters_callsServiceWithEmptySetAndReturnsJobId() { + Long jobId = 42L; + JobExecuteRequest request = JobExecuteRequest.builder().jobId(jobId).build(); + JobExecuteCommand command = new JobExecuteCommand(); + command.setPayload(request); + + JobExecuteResponse response = underTest.handle(command); + + verify(jobRegisterService).executeJobWithParameters(eq(jobId), eq(Set.of())); + assertThat(response.getResourceId()).isEqualTo(jobId); + } + + @Test + void handle_withParameters_callsServiceWithParameterSet() { + Long jobId = 42L; + JobExecuteRequest request = JobExecuteRequest.builder().jobId(jobId).parameters(java.util.List.of()).build(); + JobExecuteCommand command = new JobExecuteCommand(); + command.setPayload(request); + + JobExecuteResponse response = underTest.handle(command); + + verify(jobRegisterService).executeJobWithParameters(eq(jobId), anySet()); + assertThat(response.getResourceId()).isEqualTo(jobId); + } +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/InstanceModeIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/InstanceModeIntegrationTest.java index 8809296b884..8aa73a47d03 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/InstanceModeIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/InstanceModeIntegrationTest.java @@ -24,6 +24,7 @@ import org.apache.fineract.client.feign.util.CallFailedRuntimeException; import org.apache.fineract.client.models.GetOfficesResponse; +import org.apache.fineract.client.models.JobExecuteRequest; import org.apache.fineract.client.models.PostClientsRequest; import org.apache.fineract.client.util.Calls; import org.apache.fineract.integrationtests.common.ClientHelper; @@ -109,21 +110,23 @@ public void testCreateClientDoesntWork_WhenBatchOnly() { @Test public void testRunSchedulerJobDoesntWork_WhenReadOnly() { // when/then - assertThrows(RuntimeException.class, () -> Calls.ok(FineractClientHelper.getFineractClient().jobs.executeJob(jobId, "executeJob"))); + assertThrows(RuntimeException.class, + () -> Calls.ok(FineractClientHelper.getFineractClient().jobs.executeJob(jobId, "executeJob", new JobExecuteRequest()))); } @ConfigureInstanceMode(readEnabled = false, writeEnabled = true, batchWorkerEnabled = false, batchManagerEnabled = false) @Test public void testRunSchedulerJobDoesntWork_WhenWriteOnly() { // when/then - assertThrows(RuntimeException.class, () -> Calls.ok(FineractClientHelper.getFineractClient().jobs.executeJob(jobId, "executeJob"))); + assertThrows(RuntimeException.class, + () -> Calls.ok(FineractClientHelper.getFineractClient().jobs.executeJob(jobId, "executeJob", new JobExecuteRequest()))); } @ConfigureInstanceMode(readEnabled = false, writeEnabled = false, batchWorkerEnabled = true, batchManagerEnabled = true) @Test public void testRunSchedulerJobWorks_WhenBatchOnly() { // when - Calls.ok(FineractClientHelper.getFineractClient().jobs.executeJob(jobId, "executeJob")); + Calls.ok(FineractClientHelper.getFineractClient().jobs.executeJob(jobId, "executeJob", new JobExecuteRequest())); // then no exception thrown } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignSchedulerHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignSchedulerHelper.java index 9904ff0a8e9..22051d324b7 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignSchedulerHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignSchedulerHelper.java @@ -23,12 +23,10 @@ import java.time.Duration; import java.util.List; import org.apache.fineract.client.feign.FineractFeignClient; -import org.apache.fineract.client.feign.services.SchedulerJobApi.RetrieveHistoryQueryParams; import org.apache.fineract.client.feign.util.FeignCalls; -import org.apache.fineract.client.models.ExecuteJobRequest; -import org.apache.fineract.client.models.GetJobsJobIDJobRunHistoryResponse; import org.apache.fineract.client.models.GetJobsResponse; -import org.apache.fineract.client.models.JobDetailHistoryDataSwagger; +import org.apache.fineract.client.models.JobDetailHistoryData; +import org.apache.fineract.client.models.JobExecuteRequest; import org.awaitility.Awaitility; public class FeignSchedulerHelper { @@ -55,33 +53,25 @@ public void executeAndAwaitJob(String jobDisplayName) { .orElseThrow(() -> new RuntimeException("Job not found: " + jobDisplayName)); Long jobId = targetJob.getJobId(); - Long previousRunHistoryId = getRunHistoryId(getLatestJobRunHistory(jobId)); - FeignCalls.executeVoid(() -> fineractClient.schedulerJob().executeJob(jobId, "executeJob", new ExecuteJobRequest())); + // Capture the run history ID BEFORE triggering execution + Long previousRunHistoryId = getLastRunHistoryId(jobId); - Awaitility.await().atMost(Duration.ofMinutes(2)).pollInterval(Duration.ofSeconds(1)).pollDelay(Duration.ofSeconds(1)) - .until(() -> isNewCompletedRunHistory(jobId, previousRunHistoryId)); - } - - private boolean isNewCompletedRunHistory(Long jobId, Long previousRunHistoryId) { - JobDetailHistoryDataSwagger latestRunHistory = getLatestJobRunHistory(jobId); - if (latestRunHistory == null || latestRunHistory.getJobRunEndTime() == null) { - return false; - } - Long runHistoryId = latestRunHistory.getId(); - return runHistoryId != null && (previousRunHistoryId == null || runHistoryId > previousRunHistoryId); - } + FeignCalls.executeVoid(() -> fineractClient.schedulerJob().executeJob(jobId, "executeJob", new JobExecuteRequest())); - private Long getRunHistoryId(JobDetailHistoryDataSwagger runHistory) { - return runHistory == null ? null : runHistory.getId(); + Awaitility.await().atMost(Duration.ofMinutes(2)).pollInterval(Duration.ofSeconds(1)).pollDelay(Duration.ofSeconds(1)).until(() -> { + GetJobsResponse job = ok(() -> fineractClient.schedulerJob().retrieveOneSchedulerJob(jobId)); + JobDetailHistoryData history = job.getLastRunHistory(); + if (history == null || history.getJobRunEndTime() == null) { + return false; + } + Long runHistoryId = history.getId(); + return runHistoryId != null && (previousRunHistoryId == null || runHistoryId > previousRunHistoryId); + }); } - private JobDetailHistoryDataSwagger getLatestJobRunHistory(Long jobId) { - RetrieveHistoryQueryParams queryParams = new RetrieveHistoryQueryParams().offset(0).limit(1).orderBy("id").sortOrder("DESC"); - GetJobsJobIDJobRunHistoryResponse response = ok(() -> fineractClient.schedulerJob().retrieveHistory(jobId, queryParams)); - List pageItems = response.getPageItems(); - if (pageItems == null || pageItems.isEmpty()) { - return null; - } - return pageItems.get(0); + private Long getLastRunHistoryId(Long jobId) { + GetJobsResponse job = ok(() -> fineractClient.schedulerJob().retrieveOneSchedulerJob(jobId)); + JobDetailHistoryData history = job.getLastRunHistory(); + return history == null ? null : history.getId(); } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/SchedulerJobHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/SchedulerJobHelper.java index 71e0c2105c3..01c489f3d22 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/SchedulerJobHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/SchedulerJobHelper.java @@ -36,6 +36,7 @@ import java.util.function.Function; import java.util.stream.Collectors; import org.apache.fineract.client.models.GetJobsResponse; +import org.apache.fineract.client.models.JobExecuteRequest; import org.apache.fineract.client.models.PutJobsJobIDRequest; import org.apache.fineract.client.util.Calls; import org.hamcrest.MatcherAssert; @@ -138,7 +139,7 @@ private static String updateSchedulerJobAsJSON(final boolean active) { } public void runSchedulerJob(int jobId) { - Calls.ok(FineractClientHelper.getFineractClient().jobs.executeJob((long) jobId, "executeJob")); + Calls.ok(FineractClientHelper.getFineractClient().jobs.executeJob((long) jobId, "executeJob", new JobExecuteRequest())); } // TODO: Rewrite to use fineract-client instead!