This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 3aaa2ffe9 FINERACT-2119: Address jobs by name
3aaa2ffe9 is described below
commit 3aaa2ffe9c0fac97f688f3e244d142c808ac64f0
Author: Marta Jankovics <[email protected]>
AuthorDate: Wed Aug 28 03:09:34 2024 +0200
FINERACT-2119: Address jobs by name
---
.../db/custom-changelog/0001_acme_loan_job.xml | 6 +
.../infrastructure/core/api/IdTypeResolver.java | 74 ++++++++
.../core/exception/MultiException.java | 8 +-
.../PlatformApiDataValidationException.java | 12 ++
.../infrastructure/core/service/DefaultOption.java | 15 +-
.../infrastructure/jobs/data/JobDetailData.java | 21 +++
.../jobs/exception/JobExecutionException.java | 35 ++++
.../service/SchedulerJobRunnerReadService.java | 13 +-
.../fineract/test/config/CacheConfiguration.java | 2 +-
.../apache/fineract/test/data/job/DefaultJob.java | 18 +-
.../org/apache/fineract/test/data/job/Job.java | 2 +
.../apache/fineract/test/data/job/JobResolver.java | 17 +-
.../jobs/api/SchedulerJobApiConstants.java | 1 +
.../jobs/api/SchedulerJobApiResource.java | 138 +++++++++++----
.../jobs/api/SchedulerJobApiResourceSwagger.java | 2 +
.../jobs/domain/ScheduledJobDetail.java | 6 +-
.../jobs/domain/ScheduledJobDetailRepository.java | 20 +++
.../jobs/exception/JobNotFoundException.java | 5 +
.../service/SchedulerJobRunnerReadServiceImpl.java | 170 +++++++-----------
.../resources/db/changelog/db.changelog-master.xml | 8 +-
.../db/changelog/tenant/changelog-tenant.xml | 1 +
.../db/changelog/tenant/final-changelog-tenant.xml | 21 +--
.../changelog/tenant/parts/0145_job_short_name.xml | 196 +++++++++++++++++++++
.../tenant/parts/0146_add_final_constraints.xml | 24 +--
.../InstanceModeIntegrationTest.java | 4 +-
.../SavingsInterestPostingJobIntegrationTest.java | 17 +-
.../common/SchedulerJobHelper.java | 80 ++++++++-
27 files changed, 689 insertions(+), 227 deletions(-)
diff --git
a/custom/acme/loan/job/src/main/resources/db/custom-changelog/0001_acme_loan_job.xml
b/custom/acme/loan/job/src/main/resources/db/custom-changelog/0001_acme_loan_job.xml
index f466fdaf3..b3a8f5443 100644
---
a/custom/acme/loan/job/src/main/resources/db/custom-changelog/0001_acme_loan_job.xml
+++
b/custom/acme/loan/job/src/main/resources/db/custom-changelog/0001_acme_loan_job.xml
@@ -42,4 +42,10 @@
<column name="is_mismatched_job" valueBoolean="true"/>
</insert>
</changeSet>
+ <changeSet author="acme" id="2">
+ <update tableName="job">
+ <column name="short_name" value="ACM_NOOP"/>
+ <where>name='Acme Noop Job'</where>
+ </update>
+ </changeSet>
</databaseChangeLog>
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/api/IdTypeResolver.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/api/IdTypeResolver.java
new file mode 100644
index 000000000..28983324d
--- /dev/null
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/api/IdTypeResolver.java
@@ -0,0 +1,74 @@
+/**
+ * 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.core.api;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import
org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.service.DefaultOption;
+
+@AllArgsConstructor(access = AccessLevel.PROTECTED)
+@Getter
+public abstract class IdTypeResolver {
+
+ public enum IdType implements DefaultOption {
+
+ ID, //
+ EXTERNAL_ID, //
+ SHORT_NAME,; //
+
+ @Override
+ public boolean isDefault() {
+ return this == ID;
+ }
+
+ }
+
+ @NotNull
+ public static IdType resolveDefault() {
+ return IdType.ID;
+ }
+
+ public static IdType resolve(String idType) {
+ return resolve(IdType.class, idType);
+ }
+
+ public static <T extends Enum<T>> T resolve(@NotNull Class<T> clazz,
String idType) {
+ if (idType == null) {
+ return clazz.isAssignableFrom(DefaultOption.class) ? (T)
DefaultOption.getDefault((Class) clazz) : null;
+ }
+ idType = formatIdType(idType);
+ try {
+ return Enum.valueOf(clazz, idType);
+ } catch (IllegalArgumentException e) {
+ throw resolveFailed(idType, e);
+ }
+ }
+
+ public static String formatIdType(String idType) {
+ return idType == null ? null : idType.replaceAll("-",
"_").toUpperCase();
+ }
+
+ public static RuntimeException resolveFailed(String idType, Exception e) {
+ return new
PlatformApiDataValidationException("error.msg.id.type.not.found", "Provided
type " + idType + " is not supported",
+ "idType", e, idType);
+ }
+}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/MultiException.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/MultiException.java
index 17276de5a..b9fbab4dc 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/MultiException.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/MultiException.java
@@ -51,14 +51,18 @@ public class MultiException extends Exception {
private final List<Throwable> throwables;
@SuppressFBWarnings("CT_CONSTRUCTOR_THROW")
- public MultiException(List<Throwable> problems) {
- super("MultiException with " + problems.size() + " contained causes
(details available)");
+ public MultiException(String message, List<Throwable> problems) {
+ super(message);
if (problems.isEmpty()) {
throw new IllegalArgumentException("List of Throwables must not be
empty");
}
this.throwables = new ArrayList<>(problems);
}
+ public MultiException(List<Throwable> problems) {
+ this("MultiException with " + problems.size() + " contained causes
(details available)", problems);
+ }
+
public List<Throwable> getCauses() {
return Collections.unmodifiableList(throwables);
}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformApiDataValidationException.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformApiDataValidationException.java
index f20b4cf34..6faf601db 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformApiDataValidationException.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformApiDataValidationException.java
@@ -56,6 +56,18 @@ public class PlatformApiDataValidationException extends
AbstractPlatformExceptio
this.errors = errors;
}
+ public PlatformApiDataValidationException(String messageCode, String
userMessage, String parameterName, Throwable cause,
+ final Object... userMessageArgs) {
+ this("validation.msg.validation.errors.exist", "Validation errors
exist.",
+ List.of(ApiParameterError.parameterError(messageCode,
userMessage, parameterName, userMessageArgs)), cause);
+ }
+
+ public PlatformApiDataValidationException(String messageCode, String
userMessage, String parameterName,
+ final Object... userMessageArgs) {
+ this("validation.msg.validation.errors.exist", "Validation errors
exist.",
+ List.of(ApiParameterError.parameterError(messageCode,
userMessage, parameterName, userMessageArgs)), null);
+ }
+
public List<ApiParameterError> getErrors() {
return this.errors;
}
diff --git
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/job/Job.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DefaultOption.java
similarity index 67%
copy from
fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/job/Job.java
copy to
fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DefaultOption.java
index 76354b282..8249b961f 100644
---
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/job/Job.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DefaultOption.java
@@ -16,9 +16,18 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.fineract.test.data.job;
+package org.apache.fineract.infrastructure.core.service;
-public interface Job {
+public interface DefaultOption {
- String getName();
+ boolean isDefault();
+
+ static <T extends Enum<T> & DefaultOption> T getDefault(Class<T> clazz) {
+ for (T enumConstant : clazz.getEnumConstants()) {
+ if (enumConstant.isDefault()) {
+ return enumConstant;
+ }
+ }
+ return null;
+ }
}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobDetailData.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobDetailData.java
index e11b03dae..3e2b63692 100755
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobDetailData.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobDetailData.java
@@ -34,6 +34,9 @@ public class JobDetailData {
@SuppressWarnings("unused")
private String displayName;
+ @SuppressWarnings("unused")
+ private String shortName;
+
@SuppressWarnings("unused")
private Date nextRunTime;
@@ -50,4 +53,22 @@ public class JobDetailData {
@SuppressWarnings("unused")
private JobDetailHistoryData lastRunHistory;
+
+ public JobDetailData(Long jobId, String displayName, String shortName,
Date nextRunTime, String initializingError,
+ String cronExpression, boolean active, boolean currentlyRunning,
Long version, Date jobRunStartTime, Date jobRunEndTime,
+ String status, String jobRunErrorMessage, String triggerType,
String jobRunErrorLog) {
+ this.jobId = jobId;
+ this.displayName = displayName;
+ this.shortName = shortName;
+ this.nextRunTime = nextRunTime;
+ this.initializingError = initializingError;
+ this.cronExpression = cronExpression;
+ this.active = active;
+ this.currentlyRunning = currentlyRunning;
+ if (version != null) {
+ this.lastRunHistory = new
JobDetailHistoryData().setVersion(version).setJobRunStartTime(jobRunStartTime)
+
.setJobRunEndTime(jobRunEndTime).setStatus(status).setJobRunErrorMessage(jobRunErrorMessage).setTriggerType(triggerType)
+ .setJobRunErrorLog(jobRunErrorLog);
+ }
+ }
}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobExecutionException.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobExecutionException.java
index 02dc85776..bf7d29576 100755
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobExecutionException.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobExecutionException.java
@@ -18,11 +18,23 @@
*/
package org.apache.fineract.infrastructure.jobs.exception;
+import static java.util.stream.Collectors.mapping;
+import static java.util.stream.Collectors.toList;
+
+import jakarta.validation.constraints.NotNull;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.apache.fineract.infrastructure.core.exception.MultiException;
public class JobExecutionException extends MultiException {
+ public JobExecutionException(String message, List<Throwable> problems) {
+ super(message, problems);
+ }
+
public JobExecutionException(List<Throwable> problems) {
super(problems);
}
@@ -30,4 +42,27 @@ public class JobExecutionException extends MultiException {
public JobExecutionException(MultiException multiException) {
super(multiException.getCauses());
}
+
+ public static void throwErrors(@NotNull Map<Throwable, List<String>>
errorMap) throws JobExecutionException {
+ int size = errorMap.size();
+ if (size == 0) {
+ return;
+ }
+ List<Throwable> errors;
+ StringBuilder msg = new StringBuilder("Job failed on ");
+ Stream<Map.Entry<Throwable, List<String>>> entryStream =
errorMap.entrySet().stream().filter(e -> e.getValue() != null);
+ if (size < 10) {
+ errors = new ArrayList<>(errorMap.keySet());
+ Map<String, List<List<String>>> errorTypes = entryStream
+ .collect(Collectors.groupingBy(e ->
e.getKey().getClass().getSimpleName(), mapping(Map.Entry::getValue, toList())));
+ errorTypes.forEach((key, value) ->
msg.append(key).append(':').append(value.size()).append(':')
+
.append(value.stream().flatMap(List::stream).collect(Collectors.joining(","))).append(";\n"));
+ } else {
+ errors = List.of(errorMap.keySet().iterator().next());
+ Map<String, Long> errorTypes = entryStream
+ .collect(Collectors.groupingBy(e ->
e.getKey().getClass().getSimpleName(), Collectors.counting()));
+ errorTypes.forEach((key, value) ->
msg.append(key).append(':').append(value).append(";\n"));
+ }
+ throw new JobExecutionException(msg.toString(), errors);
+ }
}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobRunnerReadService.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobRunnerReadService.java
index f6567f717..ba40b7deb 100755
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobRunnerReadService.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobRunnerReadService.java
@@ -18,7 +18,9 @@
*/
package org.apache.fineract.infrastructure.jobs.service;
+import jakarta.validation.constraints.NotNull;
import java.util.List;
+import org.apache.fineract.infrastructure.core.api.IdTypeResolver;
import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.infrastructure.jobs.data.JobDetailData;
@@ -26,14 +28,15 @@ import
org.apache.fineract.infrastructure.jobs.data.JobDetailHistoryData;
public interface SchedulerJobRunnerReadService {
- List<JobDetailData> findAllJobDeatils();
+ List<JobDetailData> findAllJobDetails();
- JobDetailData retrieveOne(Long jobId);
+ JobDetailData retrieveOne(@NotNull IdTypeResolver.IdType idType, String
identifier);
- JobDetailData retrieveOneByName(String jobName);
+ Page<JobDetailHistoryData> retrieveJobHistory(@NotNull
IdTypeResolver.IdType idType, String identifier,
+ SearchParameters searchParameters);
- Page<JobDetailHistoryData> retrieveJobHistory(Long jobId, SearchParameters
searchParameters);
+ @NotNull
+ Long retrieveId(@NotNull IdTypeResolver.IdType idType, String identifier);
boolean isUpdatesAllowed();
-
}
diff --git
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/config/CacheConfiguration.java
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/config/CacheConfiguration.java
index 4465782c0..a9a3e485e 100644
---
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/config/CacheConfiguration.java
+++
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/config/CacheConfiguration.java
@@ -34,7 +34,7 @@ public class CacheConfiguration {
public CacheManager cacheManager() {
SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
simpleCacheManager.setCaches(List.of(new
ConcurrentMapCache("paymentTypesByName"), //
- new ConcurrentMapCache("jobsByName"), //
+ new ConcurrentMapCache("jobsByShortName"), //
new ConcurrentMapCache("loanProductsByName"), //
new ConcurrentMapCache("accountTypesByName")));//
return simpleCacheManager;
diff --git
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/job/DefaultJob.java
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/job/DefaultJob.java
index cc80f2f3f..58d2a9100 100644
---
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/job/DefaultJob.java
+++
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/job/DefaultJob.java
@@ -20,18 +20,28 @@ package org.apache.fineract.test.data.job;
public enum DefaultJob implements Job {
- ADD_ACCRUAL_TRANSACTIONS("Add Accrual Transactions"),
ADD_PERIODIC_ACCRUAL_TRANSACTIONS(
- "Add Periodic Accrual Transactions"),
INCREASE_BUSINESS_DAY("Increase Business Date by 1 day"),
LOAN_DELINQUENCY_CLASSIFICATION(
- "Loan Delinquency Classification"), LOAN_COB("Loan COB");
+ ADD_ACCRUAL_TRANSACTIONS("Add Accrual Transactions", "LA_AATR"), //
+ ADD_PERIODIC_ACCRUAL_TRANSACTIONS("Add Periodic Accrual Transactions",
"ACC_APTR"), //
+ INCREASE_BUSINESS_DAY("Increase Business Date by 1 day", "BDT_INC1"), //
+ LOAN_DELINQUENCY_CLASSIFICATION("Loan Delinquency Classification",
"LA_DECL"), //
+ LOAN_COB("Loan COB", "LA_ECOB"), //
+ ;
private final String customName;
+ private final String shortName;
- DefaultJob(String customName) {
+ DefaultJob(String customName, String shortName) {
this.customName = customName;
+ this.shortName = shortName;
}
@Override
public String getName() {
return customName;
}
+
+ @Override
+ public String getShortName() {
+ return shortName;
+ }
}
diff --git
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/job/Job.java
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/job/Job.java
index 76354b282..54cc1fd4e 100644
---
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/job/Job.java
+++
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/job/Job.java
@@ -21,4 +21,6 @@ package org.apache.fineract.test.data.job;
public interface Job {
String getName();
+
+ String getShortName();
}
diff --git
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/job/JobResolver.java
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/job/JobResolver.java
index affecbc6f..4e562ce3a 100644
---
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/job/JobResolver.java
+++
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/job/JobResolver.java
@@ -19,7 +19,6 @@
package org.apache.fineract.test.data.job;
import java.io.IOException;
-import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.client.models.GetJobsResponse;
@@ -35,20 +34,16 @@ public class JobResolver {
private final SchedulerJobApi schedulerJobApi;
- @Cacheable(key = "#job.getName()", value = "jobsByName")
+ @Cacheable(key = "#job.getShortName()", value = "jobsByShortName")
public long resolve(Job job) {
try {
- String jobName = job.getName();
- log.debug("Resolving job by name [{}]", jobName);
- Response<List<GetJobsResponse>> response =
schedulerJobApi.retrieveAll8().execute();
+ String shortName = job.getShortName();
+ log.debug("Resolving job by short-name [{}]", shortName);
+ Response<GetJobsResponse> response =
schedulerJobApi.retrieveByShortName(shortName).execute();
if (!response.isSuccessful()) {
- throw new IllegalStateException("Unable to get jobs list.
Status code was HTTP " + response.code());
+ throw new IllegalStateException("Unable to get job. Status
code was HTTP " + response.code());
}
-
- List<GetJobsResponse> jobsResponses = response.body();
- GetJobsResponse foundJob = jobsResponses.stream().filter(j ->
jobName.equals(j.getDisplayName())).findAny()
- .orElseThrow(() -> new IllegalArgumentException("Job [%s]
not found".formatted(jobName)));
- return foundJob.getJobId();
+ return response.body().getJobId();
} catch (IOException e) {
throw new RuntimeException(e);
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiConstants.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiConstants.java
index 0a09bce0b..cf4487aaa 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiConstants.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiConstants.java
@@ -54,6 +54,7 @@ public final class SchedulerJobApiConstants {
public static final String COMMAND_START_SCHEDULER = "start";
public static final String COMMAND = "command";
public static final String JOB_ID = "jobId";
+ public static final String SHORT_NAME_PARAM = "short-name";
public static final String JOB_RUN_HISTORY = "runhistory";
public static final String SCHEDULER_STATUS_PATH = "scheduler";
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 efb9b06d0..7469dd70c 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
@@ -18,6 +18,11 @@
*/
package org.apache.fineract.infrastructure.jobs.api;
+import static
org.apache.fineract.infrastructure.jobs.api.SchedulerJobApiConstants.JOB_DETAIL_RESPONSE_DATA_PARAMETERS;
+import static
org.apache.fineract.infrastructure.jobs.api.SchedulerJobApiConstants.JOB_HISTORY_RESPONSE_DATA_PARAMETERS;
+import static
org.apache.fineract.infrastructure.jobs.api.SchedulerJobApiConstants.SCHEDULER_RESOURCE_NAME;
+import static
org.apache.fineract.infrastructure.jobs.api.SchedulerJobApiConstants.SHORT_NAME_PARAM;
+
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
@@ -27,6 +32,7 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
@@ -41,12 +47,14 @@ import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import jakarta.ws.rs.core.UriInfo;
import java.util.List;
+import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.service.CommandWrapperBuilder;
import
org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.api.IdTypeResolver;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
@@ -88,10 +96,10 @@ public class SchedulerJobApiResource {
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content =
@Content(array = @ArraySchema(schema = @Schema(implementation =
SchedulerJobApiResourceSwagger.GetJobsResponse.class)))) })
public String retrieveAll(@Context final UriInfo uriInfo) {
-
this.context.authenticatedUser().validateHasReadPermission(SchedulerJobApiConstants.SCHEDULER_RESOURCE_NAME);
- final List<JobDetailData> jobDetailDatas =
this.schedulerJobRunnerReadService.findAllJobDeatils();
+
this.context.authenticatedUser().validateHasReadPermission(SCHEDULER_RESOURCE_NAME);
+ final List<JobDetailData> jobDetailDatas =
this.schedulerJobRunnerReadService.findAllJobDetails();
final ApiRequestJsonSerializationSettings settings =
this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
- return this.toApiJsonSerializer.serialize(settings, jobDetailDatas,
SchedulerJobApiConstants.JOB_DETAIL_RESPONSE_DATA_PARAMETERS);
+ return this.toApiJsonSerializer.serialize(settings, jobDetailDatas,
JOB_DETAIL_RESPONSE_DATA_PARAMETERS);
}
@GET
@@ -101,10 +109,19 @@ public class SchedulerJobApiResource {
@ApiResponse(responseCode = "200", description = "OK", content =
@Content(schema = @Schema(implementation =
SchedulerJobApiResourceSwagger.GetJobsResponse.class))) })
public String retrieveOne(@PathParam(SchedulerJobApiConstants.JOB_ID)
@Parameter(description = "jobId") final Long jobId,
@Context final UriInfo uriInfo) {
-
this.context.authenticatedUser().validateHasReadPermission(SchedulerJobApiConstants.SCHEDULER_RESOURCE_NAME);
- final JobDetailData jobDetailData =
this.schedulerJobRunnerReadService.retrieveOne(jobId);
- final ApiRequestJsonSerializationSettings settings =
this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
- return this.toApiJsonSerializer.serialize(settings, jobDetailData,
SchedulerJobApiConstants.JOB_DETAIL_RESPONSE_DATA_PARAMETERS);
+ return retrieveOne(IdTypeResolver.resolveDefault(),
Objects.toString(jobId, null), uriInfo);
+ }
+
+ @GET
+ @Path(SHORT_NAME_PARAM + "/{shortName}")
+ @Operation(summary = "Retrieve a Job", description = "Returns the details
of a Job bu shortName.\n" + "\n" + "Example Requests:\n"
+ + "\n" + "jobs/short-name/SA_PINT")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK", content =
@Content(schema = @Schema(implementation =
SchedulerJobApiResourceSwagger.GetJobsResponse.class))) })
+ public String retrieveByShortName(
+ @PathParam("shortName") @Parameter(required = true, description =
SchedulerJobApiConstants.SHORT_NAME_PARAM) final String shortName,
+ @Context final UriInfo uriInfo) {
+ return retrieveOne(IdTypeResolver.resolve(SHORT_NAME_PARAM),
shortName, uriInfo);
}
@GET
@@ -118,16 +135,22 @@ public class SchedulerJobApiResource {
@QueryParam("limit") @Parameter(description = "limit") final
Integer limit,
@QueryParam("orderBy") @Parameter(description = "orderBy") final
String orderBy,
@QueryParam("sortOrder") @Parameter(description = "sortOrder")
final String sortOrder) {
-
this.context.authenticatedUser().validateHasReadPermission(SchedulerJobApiConstants.SCHEDULER_RESOURCE_NAME);
- sqlValidator.validate(orderBy);
- sqlValidator.validate(sortOrder);
- final SearchParameters searchParameters =
SearchParameters.builder().limit(limit).offset(offset).orderBy(orderBy)
- .sortOrder(sortOrder).build();
- final Page<JobDetailHistoryData> jobhistoryDetailData =
this.schedulerJobRunnerReadService.retrieveJobHistory(jobId,
- searchParameters);
- final ApiRequestJsonSerializationSettings settings =
this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
- return this.jobHistoryToApiJsonSerializer.serialize(settings,
jobhistoryDetailData,
- SchedulerJobApiConstants.JOB_HISTORY_RESPONSE_DATA_PARAMETERS);
+ return retrieveHistory(IdTypeResolver.resolveDefault(),
Objects.toString(jobId, null), offset, limit, orderBy, sortOrder, uriInfo);
+ }
+
+ @GET
+ @Path(SHORT_NAME_PARAM + "/{shortName}/" +
SchedulerJobApiConstants.JOB_RUN_HISTORY)
+ @Operation(summary = "Retrieve Job Run History", description = "Example
Requests:\n" + "\n"
+ + "jobs/short-name/SA_PINT/runhistory?offset=0&limit=200")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK", content =
@Content(schema = @Schema(implementation =
SchedulerJobApiResourceSwagger.GetJobsJobIDJobRunHistoryResponse.class))) })
+ public String retrieveHistoryByShortName(@Context final UriInfo uriInfo,
+ @PathParam("shortName") @Parameter(required = true, description =
SchedulerJobApiConstants.SHORT_NAME_PARAM) final String shortName,
+ @QueryParam("offset") @Parameter(description = "offset") final
Integer offset,
+ @QueryParam("limit") @Parameter(description = "limit") final
Integer limit,
+ @QueryParam("orderBy") @Parameter(description = "orderBy") final
String orderBy,
+ @QueryParam("sortOrder") @Parameter(description = "sortOrder")
final String sortOrder) {
+ return retrieveHistory(IdTypeResolver.resolve(SHORT_NAME_PARAM),
shortName, offset, limit, orderBy, sortOrder, uriInfo);
}
@POST
@@ -138,17 +161,78 @@ public class SchedulerJobApiResource {
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) {
- // check the logged in user have permissions to execute scheduler jobs
+ return executeJob(IdTypeResolver.resolveDefault(),
Objects.toString(jobId, null), commandParam, jsonRequestBody);
+ }
+
+ @POST
+ @Path(SHORT_NAME_PARAM + "/{shortName}")
+ @Operation(summary = "Run a Job", description = "Manually Execute Specific
Job.")
+ @RequestBody(content = @Content(schema = @Schema(implementation =
SchedulerJobApiResourceSwagger.ExecuteJobRequest.class)))
+ @ApiResponses({ @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);
+ }
+
+ @PUT
+ @Path("{" + SchedulerJobApiConstants.JOB_ID + "}")
+ @Operation(summary = "Update a Job", description = "Updates the details of
a job.")
+ @RequestBody(required = true, content = @Content(schema =
@Schema(implementation =
SchedulerJobApiResourceSwagger.PutJobsJobIDRequest.class)))
+ @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
+ public String updateJobDetail(@PathParam(SchedulerJobApiConstants.JOB_ID)
@Parameter(description = "jobId") final Long jobId,
+ @Parameter(hidden = true) final String jsonRequestBody) {
+ return updateJobDetail(IdTypeResolver.resolveDefault(),
Objects.toString(jobId, null), jsonRequestBody);
+ }
+
+ @PUT
+ @Path(SHORT_NAME_PARAM + "/{shortName}")
+ @Operation(summary = "Update a Job", description = "Updates the details of
a job.")
+ @RequestBody(required = true, content = @Content(schema =
@Schema(implementation =
SchedulerJobApiResourceSwagger.PutJobsJobIDRequest.class)))
+ @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
+ public String updateJobDetailByShortName(
+ @PathParam("shortName") @Parameter(required = true, description =
SchedulerJobApiConstants.SHORT_NAME_PARAM) final String shortName,
+ @Parameter(hidden = true) final String jsonRequestBody) {
+ return updateJobDetail(IdTypeResolver.resolve(SHORT_NAME_PARAM),
shortName, jsonRequestBody);
+ }
+
+ private boolean is(final String commandParam, final String commandValue) {
+ return StringUtils.isNotBlank(commandParam) &&
commandParam.trim().equalsIgnoreCase(commandValue);
+ }
+
+ private String retrieveOne(@NotNull IdTypeResolver.IdType idType, String
identifier, UriInfo uriInfo) {
+
context.authenticatedUser().validateHasReadPermission(SCHEDULER_RESOURCE_NAME);
+ final JobDetailData jobDetailData =
schedulerJobRunnerReadService.retrieveOne(idType, identifier);
+ final ApiRequestJsonSerializationSettings settings =
apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return toApiJsonSerializer.serialize(settings, jobDetailData,
JOB_DETAIL_RESPONSE_DATA_PARAMETERS);
+ }
+
+ private String retrieveHistory(@NotNull IdTypeResolver.IdType idType,
String identifier, Integer offset, Integer limit, String orderBy,
+ String sortOrder, UriInfo uriInfo) {
+
context.authenticatedUser().validateHasReadPermission(SCHEDULER_RESOURCE_NAME);
+ sqlValidator.validate(orderBy);
+ sqlValidator.validate(sortOrder);
+ final SearchParameters searchParameters =
SearchParameters.builder().limit(limit).offset(offset).orderBy(orderBy)
+ .sortOrder(sortOrder).build();
+ final Page<JobDetailHistoryData> jobHistoryData =
schedulerJobRunnerReadService.retrieveJobHistory(idType, identifier,
+ searchParameters);
+ final ApiRequestJsonSerializationSettings settings =
apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ 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 =
this.context.authenticatedUser().hasNotPermissionForAnyOf("ALL_FUNCTIONS",
- "EXECUTEJOB_SCHEDULER");
+ 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);
jobRegisterService.executeJobWithParameters(jobId,
jsonRequestBody);
response = Response.status(202).build();
} else {
@@ -161,14 +245,8 @@ public class SchedulerJobApiResource {
return response;
}
- @PUT
- @Path("{" + SchedulerJobApiConstants.JOB_ID + "}")
- @Operation(summary = "Update a Job", description = "Updates the details of
a job.")
- @RequestBody(required = true, content = @Content(schema =
@Schema(implementation =
SchedulerJobApiResourceSwagger.PutJobsJobIDRequest.class)))
- @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
- public String updateJobDetail(@PathParam(SchedulerJobApiConstants.JOB_ID)
@Parameter(description = "jobId") final Long jobId,
- @Parameter(hidden = true) final String jsonRequestBody) {
-
+ private String updateJobDetail(@NotNull IdTypeResolver.IdType idType,
String identifier, String jsonRequestBody) {
+ Long jobId = schedulerJobRunnerReadService.retrieveId(idType,
identifier);
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
.updateJobDetail(jobId) //
.withJson(jsonRequestBody) //
@@ -180,8 +258,4 @@ public class SchedulerJobApiResource {
}
return this.toApiJsonSerializer.serialize(result);
}
-
- private boolean is(final String commandParam, final String commandValue) {
- return StringUtils.isNotBlank(commandParam) &&
commandParam.trim().equalsIgnoreCase(commandValue);
- }
}
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 81bfec247..a981a4109 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
@@ -44,6 +44,8 @@ final class SchedulerJobApiResourceSwagger {
public Long jobId;
@Schema(example = "Update loan Summary")
public String displayName;
+ @Schema(example = "LA_USUM")
+ public String shortName;
@Schema(example = "")
public Date nextRunTime;
@Schema(example = "")
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobDetail.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobDetail.java
index 036b8536b..a5b2d8d5a 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobDetail.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobDetail.java
@@ -23,6 +23,7 @@ import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
+import jakarta.persistence.UniqueConstraint;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -36,7 +37,7 @@ import
org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.infrastructure.jobs.api.SchedulerJobApiConstants;
@Entity
-@Table(name = "job")
+@Table(name = "job", uniqueConstraints = { @UniqueConstraint(columnNames = {
"short_name" }, name = "job_short_name_key") })
@Getter
@Setter
@NoArgsConstructor
@@ -97,6 +98,9 @@ public class ScheduledJobDetail extends
AbstractPersistableCustom<Long> {
@Column(name = "is_misfired")
private boolean triggerMisfired;
+ @Column(name = "short_name", nullable = false)
+ private String shortName;
+
public Map<String, Object> update(final JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(9);
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobDetailRepository.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobDetailRepository.java
index 6440be79b..be3d4cc4b 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobDetailRepository.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobDetailRepository.java
@@ -20,6 +20,8 @@ package org.apache.fineract.infrastructure.jobs.domain;
import jakarta.persistence.LockModeType;
import java.util.List;
+import java.util.Optional;
+import org.apache.fineract.infrastructure.jobs.data.JobDetailData;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Lock;
@@ -47,4 +49,22 @@ public interface ScheduledJobDetailRepository
ScheduledJobDetail findByJobName(String jobName);
+ String GET_DATA = "select new
org.apache.fineract.infrastructure.jobs.data.JobDetailData(j.id,
j.jobDisplayName, j.shortName, j.nextRunTime, "
+ + "j.errorLog, j.cronExpression, j.activeSchedular,
j.currentlyRunning, "
+ + "jh.version, jh.startTime, jh.endTime, jh.status,
jh.errorMessage, jh.triggerType, jh.errorLog) "
+ + "from ScheduledJobDetail j left join ScheduledJobRunHistory jh
on jh.scheduledJobDetail = j and j.previousRunStartTime = jh.startTime ";
+
+ @Query(GET_DATA + "where j.id = :jobId")
+ JobDetailData getDataById(@Param("jobId") Long jobId);
+
+ @Query(GET_DATA + "where j.shortName = :shortName")
+ JobDetailData getDataByShortName(@Param("shortName") String shortName);
+
+ @Query(GET_DATA + "order by j.id")
+ List<JobDetailData> getAllData();
+
+ boolean existsByShortName(String shortName);
+
+ @Query("select j.id from ScheduledJobDetail j where j.shortName =
:shortName")
+ Optional<Long> findIdByShortName(@Param("shortName") String shortName);
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobNotFoundException.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobNotFoundException.java
index becff1983..95c9b493e 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobNotFoundException.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobNotFoundException.java
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.infrastructure.jobs.exception;
+import org.apache.fineract.infrastructure.core.api.IdTypeResolver;
import
org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.dao.EmptyResultDataAccessException;
@@ -31,6 +32,10 @@ public class JobNotFoundException extends
AbstractPlatformResourceNotFoundExcept
super("error.msg.sheduler.job.id.invalid", "Job with identifier " +
identifier + " does not exist", identifier);
}
+ public JobNotFoundException(IdTypeResolver.IdType idType, String jobId) {
+ super("error.msg.sheduler.job.id.invalid", String.format("Job with %s:
%s cannot be found", idType, jobId), idType, jobId);
+ }
+
public JobNotFoundException(final String identifier, NoSuchJobException e)
{
super("error.msg.sheduler.job.id.invalid", "Job with identifier " +
identifier + " does not exist", identifier, e);
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobRunnerReadServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobRunnerReadServiceImpl.java
index 70ef390fe..8f811e6b8 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobRunnerReadServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobRunnerReadServiceImpl.java
@@ -18,22 +18,24 @@
*/
package org.apache.fineract.infrastructure.jobs.service;
+import jakarta.validation.constraints.NotNull;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.infrastructure.core.api.IdTypeResolver;
import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.PaginationHelper;
import org.apache.fineract.infrastructure.core.service.SearchParameters;
import
org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.jobs.data.JobDetailData;
import org.apache.fineract.infrastructure.jobs.data.JobDetailHistoryData;
+import
org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetailRepository;
import org.apache.fineract.infrastructure.jobs.exception.JobNotFoundException;
import
org.apache.fineract.infrastructure.jobs.exception.OperationNotAllowedException;
import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@@ -47,59 +49,62 @@ public class SchedulerJobRunnerReadServiceImpl implements
SchedulerJobRunnerRead
private final JdbcTemplate jdbcTemplate;
private final ColumnValidator columnValidator;
private final DatabaseSpecificSQLGenerator sqlGenerator;
+ private final ScheduledJobDetailRepository jobDetailRepository;
private final PaginationHelper paginationHelper;
@Autowired
public SchedulerJobRunnerReadServiceImpl(final JdbcTemplate jdbcTemplate,
final ColumnValidator columnValidator,
- DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper
paginationHelper) {
+ DatabaseSpecificSQLGenerator sqlGenerator,
ScheduledJobDetailRepository jobDetailRepository,
+ PaginationHelper paginationHelper) {
this.jdbcTemplate = jdbcTemplate;
this.columnValidator = columnValidator;
this.sqlGenerator = sqlGenerator;
+ this.jobDetailRepository = jobDetailRepository;
this.paginationHelper = paginationHelper;
}
@Override
- public List<JobDetailData> findAllJobDeatils() {
- final JobDetailMapper detailMapper = new JobDetailMapper(sqlGenerator);
- final String sql = detailMapper.schema();
- final List<JobDetailData> JobDeatils = this.jdbcTemplate.query(sql,
detailMapper, new Object[] {});
- return JobDeatils;
+ public List<JobDetailData> findAllJobDetails() {
+ return jobDetailRepository.getAllData();
}
@Override
- public JobDetailData retrieveOne(final Long jobId) {
- try {
- final JobDetailMapper detailMapper = new
JobDetailMapper(sqlGenerator);
- final String sql = detailMapper.schema() + " where job.id=?";
- return this.jdbcTemplate.queryForObject(sql, detailMapper, new
Object[] { jobId }); // NOSONAR
- } catch (final EmptyResultDataAccessException e) {
- throw new JobNotFoundException(String.valueOf(jobId), e);
+ public JobDetailData retrieveOne(@NotNull IdTypeResolver.IdType idType,
String identifier) {
+ JobDetailData jobDetail = switch (idType) {
+ case ID ->
jobDetailRepository.getDataById(Long.valueOf(identifier));
+ case SHORT_NAME ->
jobDetailRepository.getDataByShortName(identifier);
+ default -> null;
+ };
+ if (jobDetail == null) {
+ throw new JobNotFoundException(idType, identifier);
}
+ return jobDetail;
}
@Override
- public JobDetailData retrieveOneByName(String jobName) {
- try {
- final JobDetailMapper detailMapper = new
JobDetailMapper(sqlGenerator);
- final String sql = detailMapper.schema() + " where job.name=?";
- return this.jdbcTemplate.queryForObject(sql, detailMapper, new
Object[] { jobName }); // NOSONAR
- } catch (final EmptyResultDataAccessException e) {
- throw new JobNotFoundException(jobName, e);
- }
- }
-
- @Override
- public Page<JobDetailHistoryData> retrieveJobHistory(final Long jobId,
final SearchParameters searchParameters) {
- if (!isJobExist(jobId)) {
- throw new JobNotFoundException(String.valueOf(jobId));
+ public Page<JobDetailHistoryData> retrieveJobHistory(@NotNull
IdTypeResolver.IdType idType, String identifier,
+ SearchParameters searchParameters) {
+ if (!isJobExist(idType, identifier)) {
+ throw new JobNotFoundException(idType, identifier);
}
final JobHistoryMapper jobHistoryMapper = new
JobHistoryMapper(sqlGenerator);
- final StringBuilder sqlBuilder = new StringBuilder(200);
- sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " ");
- sqlBuilder.append(jobHistoryMapper.schema());
- sqlBuilder.append(" where job.id=?");
+ final StringBuilder sqlBuilder = new StringBuilder("select " +
sqlGenerator.calcFoundRows() + " ").append(jobHistoryMapper.schema())
+ .append(" where job.");
+ Object idParam;
+ switch (idType) {
+ case ID -> {
+ sqlBuilder.append("id");
+ idParam = Long.valueOf(identifier);
+ }
+ case SHORT_NAME -> {
+ sqlBuilder.append("short_name");
+ idParam = identifier;
+ }
+ default -> throw new JobNotFoundException(idType, identifier);
+ }
+ sqlBuilder.append(" = ?");
if (searchParameters.hasOrderBy()) {
sqlBuilder.append(" order by
").append(searchParameters.getOrderBy());
this.columnValidator.validateSqlInjection(sqlBuilder.toString(),
searchParameters.getOrderBy());
@@ -118,7 +123,18 @@ public class SchedulerJobRunnerReadServiceImpl implements
SchedulerJobRunnerRead
}
}
- return this.paginationHelper.fetchPage(this.jdbcTemplate,
sqlBuilder.toString(), new Object[] { jobId }, jobHistoryMapper);
+ return this.paginationHelper.fetchPage(this.jdbcTemplate,
sqlBuilder.toString(), new Object[] { idParam }, jobHistoryMapper);
+ }
+
+ @Override
+ @NotNull
+ public Long retrieveId(@NotNull IdTypeResolver.IdType idType, String
identifier) {
+ return switch (idType) {
+ case ID -> Long.valueOf(identifier);
+ case SHORT_NAME ->
+
jobDetailRepository.findIdByShortName(identifier).orElseThrow(() -> new
JobNotFoundException(idType, identifier));
+ default -> throw new JobNotFoundException(idType, identifier);
+ };
}
@Override
@@ -133,84 +149,27 @@ public class SchedulerJobRunnerReadServiceImpl implements
SchedulerJobRunnerRead
return true;
}
- private boolean isJobExist(final Long jobId) {
- boolean isJobPresent = false;
- try {
- final String sql = "select count(*) from job job where job.id= ?";
- final int count = this.jdbcTemplate.queryForObject(sql,
Integer.class, new Object[] { jobId });
- if (count == 1) {
- isJobPresent = true;
- }
- return isJobPresent;
- } catch (EmptyResultDataAccessException e) {
- return isJobPresent;
- }
-
- }
-
- private static final class JobDetailMapper implements
RowMapper<JobDetailData> {
-
- private final StringBuilder sqlBuilder;
-
- JobDetailMapper(DatabaseSpecificSQLGenerator sqlGenerator) {
- sqlBuilder = new StringBuilder("select").append(
- " job.id,job.display_name as displayName,job.next_run_time
as nextRunTime,job.initializing_errorlog as
initializingError,job.cron_expression as cronExpression,job.is_active as
active,job.currently_running as currentlyRunning,")
- .append(" runHistory.version,runHistory.start_time as
lastRunStartTime,runHistory.end_time as lastRunEndTime,runHistory.")
- .append(sqlGenerator.escape("status"))
- .append(",runHistory.error_message as
jobRunErrorMessage,runHistory.trigger_type as triggerType,runHistory.error_log
as jobRunErrorLog ")
- .append(" from job job left join job_run_history
runHistory ON job.id=runHistory.job_id and
job.previous_run_start_time=runHistory.start_time ");
- }
-
- public String schema() {
- return this.sqlBuilder.toString();
- }
-
- @Override
- public JobDetailData mapRow(final ResultSet rs,
@SuppressWarnings("unused") final int rowNum) throws SQLException {
- final Long id = rs.getLong("id");
- final String displayName = rs.getString("displayName");
- final Date nextRunTime = rs.getTimestamp("nextRunTime");
- final String initializingError = rs.getString("initializingError");
- final String cronExpression = rs.getString("cronExpression");
- final boolean active = rs.getBoolean("active");
- final boolean currentlyRunning = rs.getBoolean("currentlyRunning");
-
- final Long version = rs.getLong("version");
- final Date jobRunStartTime = rs.getTimestamp("lastRunStartTime");
- final Date jobRunEndTime = rs.getTimestamp("lastRunEndTime");
- final String status = rs.getString("status");
- final String jobRunErrorMessage =
rs.getString("jobRunErrorMessage");
- final String triggerType = rs.getString("triggerType");
- final String jobRunErrorLog = rs.getString("jobRunErrorLog");
-
- JobDetailHistoryData lastRunHistory = null;
- if (version > 0) {
- lastRunHistory = new
JobDetailHistoryData().setVersion(version).setJobRunStartTime(jobRunStartTime)
-
.setJobRunEndTime(jobRunEndTime).setStatus(status).setJobRunErrorMessage(jobRunErrorMessage)
-
.setTriggerType(triggerType).setJobRunErrorLog(jobRunErrorLog);
- }
- final JobDetailData jobDetail = new
JobDetailData().setJobId(id).setDisplayName(displayName).setNextRunTime(nextRunTime)
-
.setInitializingError(initializingError).setCronExpression(cronExpression).setActive(active)
-
.setCurrentlyRunning(currentlyRunning).setLastRunHistory(lastRunHistory);
- return jobDetail;
- }
-
+ private boolean isJobExist(@NotNull IdTypeResolver.IdType idType, @NotNull
String jobId) {
+ return switch (idType) {
+ case ID -> jobDetailRepository.existsById(Long.valueOf(jobId));
+ case SHORT_NAME -> jobDetailRepository.existsByShortName(jobId);
+ default -> false;
+ };
}
private static final class JobHistoryMapper implements
RowMapper<JobDetailHistoryData> {
- private final StringBuilder sqlBuilder;
+ private final String sql;
JobHistoryMapper(DatabaseSpecificSQLGenerator sqlGenerator) {
- sqlBuilder = new StringBuilder(200)
- .append(" runHistory.version,runHistory.start_time as
runStartTime,runHistory.end_time as runEndTime,runHistory."
- + sqlGenerator.escape("status")
- + ",runHistory.error_message as
jobRunErrorMessage,runHistory.trigger_type as triggerType,runHistory.error_log
as jobRunErrorLog ")
- .append(" from job job join job_run_history runHistory ON
job.id=runHistory.job_id");
+ sql = " runHistory.version, runHistory.start_time as runStartTime,
runHistory.end_time as runEndTime, " + "runHistory."
+ + sqlGenerator.escape("status")
+ + ", runHistory.error_message as jobRunErrorMessage,
runHistory.trigger_type as triggerType, runHistory.error_log as jobRunErrorLog "
+ + "from job job join job_run_history runHistory ON
job.id=runHistory.job_id";
}
public String schema() {
- return this.sqlBuilder.toString();
+ return sql;
}
@Override
@@ -222,12 +181,9 @@ public class SchedulerJobRunnerReadServiceImpl implements
SchedulerJobRunnerRead
final String jobRunErrorMessage =
rs.getString("jobRunErrorMessage");
final String triggerType = rs.getString("triggerType");
final String jobRunErrorLog = rs.getString("jobRunErrorLog");
- final JobDetailHistoryData jobDetailHistory = new
JobDetailHistoryData().setVersion(version).setJobRunStartTime(jobRunStartTime)
-
.setJobRunEndTime(jobRunEndTime).setStatus(status).setJobRunErrorMessage(jobRunErrorMessage).setTriggerType(triggerType)
+ return new
JobDetailHistoryData().setVersion(version).setJobRunStartTime(jobRunStartTime).setJobRunEndTime(jobRunEndTime)
+
.setStatus(status).setJobRunErrorMessage(jobRunErrorMessage).setTriggerType(triggerType)
.setJobRunErrorLog(jobRunErrorLog);
- return jobDetailHistory;
}
-
}
-
}
diff --git
a/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml
b/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml
index 4edca24bf..fd9f869f1 100644
--- a/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml
+++ b/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml
@@ -31,6 +31,10 @@
<include file="tenant-store/changelog-tenant-store.xml"
relativeToChangelogFile="true" context="tenant_store_db AND !initial_switch"/>
<include file="tenant/initial-switch-changelog-tenant.xml"
relativeToChangelogFile="true" context="tenant_db AND initial_switch"/>
<include file="tenant/changelog-tenant.xml" relativeToChangelogFile="true"
context="tenant_db AND !initial_switch"/>
- <includeAll path="db/changelog/tenant/module"
errorIfMissingOrEmpty="false" context="tenant_db AND custom_changelog"
filter="org.apache.fineract.infrastructure.core.service.migration.TenantModuleRootFilter"
/>
- <includeAll path="db/custom-changelog" errorIfMissingOrEmpty="false"
context="tenant_db AND custom_changelog" />
+ <!-- Add new module to the end of this modules list (to keep the existing
auto-increment identifiers) -->
+ <include
file="db/changelog/tenant/module/loan/module-changelog-master.xml"
context="tenant_db AND !initial_switch"/>
+ <include
file="db/changelog/tenant/module/investor/module-changelog-master.xml"
context="tenant_db AND !initial_switch"/>
+ <includeAll path="db/custom-changelog" errorIfMissingOrEmpty="false"
context="tenant_db AND !initial_switch AND custom_changelog"/>
+ <!-- Scripts to run after the modules were initialized -->
+ <include file="tenant/final-changelog-tenant.xml"
relativeToChangelogFile="true" context="tenant_db AND !initial_switch"/>
</databaseChangeLog>
diff --git
a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index ea46e8546..cf753dc26 100644
---
a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++
b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -164,4 +164,5 @@
<include file="parts/0142_add_accrual_activity_transaction.xml"
relativeToChangelogFile="true" />
<include file="parts/0143_add_accrual_activity_posting_job.xml"
relativeToChangelogFile="true" />
<include
file="parts/0144_transaction_summary_with_asset_owner_report_unc_allocation_fix.xml"
relativeToChangelogFile="true" />
+ <include file="parts/0145_job_short_name.xml"
relativeToChangelogFile="true" />
</databaseChangeLog>
diff --git
a/custom/acme/loan/job/src/main/resources/db/custom-changelog/0001_acme_loan_job.xml
b/fineract-provider/src/main/resources/db/changelog/tenant/final-changelog-tenant.xml
similarity index 52%
copy from
custom/acme/loan/job/src/main/resources/db/custom-changelog/0001_acme_loan_job.xml
copy to
fineract-provider/src/main/resources/db/changelog/tenant/final-changelog-tenant.xml
index f466fdaf3..27b3335ca 100644
---
a/custom/acme/loan/job/src/main/resources/db/custom-changelog/0001_acme_loan_job.xml
+++
b/fineract-provider/src/main/resources/db/changelog/tenant/final-changelog-tenant.xml
@@ -22,24 +22,5 @@
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
- <changeSet author="acme" id="1">
- <insert tableName="job">
- <column name="name" value="Acme Noop Job"/>
- <column name="display_name" value="Acme Noop Job"/>
- <column name="cron_expression" value="0 1 0 1/1 * ? *"/>
- <column name="create_time" valueDate="${current_datetime}"/>
- <column name="task_priority" valueNumeric="5"/>
- <column name="group_name"/>
- <column name="previous_run_start_time"/>
- <column name="job_key" value="Acme Noop Job _ DEFAULT"/>
- <column name="initializing_errorlog"/>
- <column name="is_active" valueBoolean="false"/>
- <column name="currently_running" valueBoolean="false"/>
- <column name="updates_allowed" valueBoolean="true"/>
- <column name="scheduler_group" valueNumeric="0"/>
- <column name="is_misfired" valueBoolean="false"/>
- <column name="node_id" valueNumeric="1"/>
- <column name="is_mismatched_job" valueBoolean="true"/>
- </insert>
- </changeSet>
+ <include file="parts/0146_add_final_constraints.xml"
relativeToChangelogFile="true"/>
</databaseChangeLog>
diff --git
a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0145_job_short_name.xml
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0145_job_short_name.xml
new file mode 100644
index 000000000..5f1f87472
--- /dev/null
+++
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0145_job_short_name.xml
@@ -0,0 +1,196 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
+ <changeSet author="fineract" id="1">
+ <addColumn tableName="job">
+ <column name="short_name" type="VARCHAR(8)">
+ <constraints unique="true" nullable="true"/>
+ </column>
+ </addColumn>
+ </changeSet>
+ <changeSet author="fineract" id="2">
+ <update tableName="job">
+ <column name="short_name" value="LA_USUM"/>
+ <where>name='Update loan Summary'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="LA_ARAG"/>
+ <where>name='Update Loan Arrears Ageing'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="LA_PIAD"/>
+ <where>name='Update Loan Paid In Advance'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="LA_AHOL"/>
+ <where>name='Apply Holidays To Loans'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="LA_TFFS"/>
+ <where>name='Transfer Fee For Loans From Savings'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="LA_OPEN"/>
+ <where>name='Apply penalty to overdue loans'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="LA_UNPA"/>
+ <where>name='Update Non Performing Assets'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="LA_RINT"/>
+ <where>name='Recalculate Interest For Loans'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="LA_GLPR"/>
+ <where>name='Generate Loan Loss Provisioning'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="LA_AATR"/>
+ <where>name='Add Accrual Transactions For Loans With Income Posted
As Transactions'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="LA_ECOB"/>
+ <where>name='Loan COB'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="LA_DECL"/>
+ <where>name='Loan Delinquency Classification'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="SA_AANF"/>
+ <where>name='Apply Annual Fee For Savings'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="SA_PINT"/>
+ <where>name='Post Interest For Savings'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="SA_PDCH"/>
+ <where>name='Pay Due Savings Charges'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="SA_TINT"/>
+ <where>name='Transfer Interest To Savings'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="SA_MATD"/>
+ <where>name='Update Deposit Accounts Maturity details'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="SA_GSCH"/>
+ <where>name='Generate Mandatory Savings Schedule'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="SA_UDOR"/>
+ <where>name='Update Savings Dormant Accounts'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="SH_PDIV"/>
+ <where>name='Post Dividends For Shares'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="ACC_RBAL"/>
+ <where>name='Update Accounting Running Balances'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="ACC_AATR"/>
+ <where>name='Add Accrual Transactions'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="ACC_APTR"/>
+ <where>name='Add Periodic Accrual Transactions'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="STI_EXEC"/>
+ <where>name='Execute Standing Instruction'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="RMJ_EXEC"/>
+ <where>name='Execute Report Mailing Jobs'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="EM_EXEC"/>
+ <where>name='Execute Email'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="EM_UOUT"/>
+ <where>name='Update Email Outbound with campaign message'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="SMS_UOUT"/>
+ <where>name='Update SMS Outbound with Campaign Message'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="SMS_SMSG"/>
+ <where>name='Send Messages to SMS Gateway'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="SMS_DRPT"/>
+ <where>name='Get Delivery Reports from SMS Gateway'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="ADH_GSCH"/>
+ <where>name='Generate AdhocClient Schedule'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="TBL_UDET"/>
+ <where>name='Update Trial Balance Details'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="JOB_EXEC"/>
+ <where>name='Execute All Dirty Jobs'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="BDT_INC1"/>
+ <where>name='Increase Business Date by 1 day'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="BDT_INC1"/>
+ <where>name='Increase Business Date by 1 day'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="BDT_COB1"/>
+ <where>name='Increase COB Date by 1 day'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="ASE_SEND"/>
+ <where>name='Send Asynchronous Events'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="EXE_PURG"/>
+ <where>name='Purge External Events'</where>
+ </update>
+ <update tableName="job">
+ <column name="short_name" value="COM_PURG"/>
+ <where>name='Purge Processed Commands'</where>
+ </update>
+ </changeSet>
+ <changeSet author="fineract" id="3">
+ <update tableName="job">
+ <column name="short_name" value="ACC_ACPO"/>
+ <where>name='Accrual Activity Posting'</where>
+ </update>
+ </changeSet>
+</databaseChangeLog>
diff --git
a/custom/acme/loan/job/src/main/resources/db/custom-changelog/0001_acme_loan_job.xml
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0146_add_final_constraints.xml
similarity index 52%
copy from
custom/acme/loan/job/src/main/resources/db/custom-changelog/0001_acme_loan_job.xml
copy to
fineract-provider/src/main/resources/db/changelog/tenant/parts/0146_add_final_constraints.xml
index f466fdaf3..e2f21abaa 100644
---
a/custom/acme/loan/job/src/main/resources/db/custom-changelog/0001_acme_loan_job.xml
+++
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0146_add_final_constraints.xml
@@ -22,24 +22,10 @@
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
- <changeSet author="acme" id="1">
- <insert tableName="job">
- <column name="name" value="Acme Noop Job"/>
- <column name="display_name" value="Acme Noop Job"/>
- <column name="cron_expression" value="0 1 0 1/1 * ? *"/>
- <column name="create_time" valueDate="${current_datetime}"/>
- <column name="task_priority" valueNumeric="5"/>
- <column name="group_name"/>
- <column name="previous_run_start_time"/>
- <column name="job_key" value="Acme Noop Job _ DEFAULT"/>
- <column name="initializing_errorlog"/>
- <column name="is_active" valueBoolean="false"/>
- <column name="currently_running" valueBoolean="false"/>
- <column name="updates_allowed" valueBoolean="true"/>
- <column name="scheduler_group" valueNumeric="0"/>
- <column name="is_misfired" valueBoolean="false"/>
- <column name="node_id" valueNumeric="1"/>
- <column name="is_mismatched_job" valueBoolean="true"/>
- </insert>
+ <changeSet author="fineract" id="1" context="postgresql">
+ <addNotNullConstraint tableName="job" columnName="short_name"/>
+ </changeSet>
+ <changeSet author="fineract" id="2" context="mysql">
+ <addNotNullConstraint tableName="job" columnName="short_name"
columnDataType="VARCHAR(8)"/>
</changeSet>
</databaseChangeLog>
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 251490c4c..a1d327674 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
@@ -57,8 +57,8 @@ public class InstanceModeIntegrationTest {
responseSpec405 = new
ResponseSpecBuilder().expectStatusCode(405).build();
schedulerJobHelper = new SchedulerJobHelper(requestSpec);
- String jobName = "Apply Annual Fee For Savings";
- jobId = schedulerJobHelper.getSchedulerJobIdByName(jobName);
+ // Apply Annual Fee For Savings"
+ jobId = schedulerJobHelper.getSchedulerJobIdByShortName("SA_AANF");
}
@ConfigureInstanceMode(readEnabled = true, writeEnabled = false,
batchWorkerEnabled = false, batchManagerEnabled = false)
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsInterestPostingJobIntegrationTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsInterestPostingJobIntegrationTest.java
index 61ee96503..2f77b7c1b 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsInterestPostingJobIntegrationTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsInterestPostingJobIntegrationTest.java
@@ -59,6 +59,7 @@ public class SavingsInterestPostingJobIntegrationTest {
private static final Logger LOG =
LoggerFactory.getLogger(SavingsInterestPostingJobIntegrationTest.class);
public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
+ public static final String POST_INTEREST_FOR_SAVINGS_JOB_SHORT_NAME =
"SA_PINT";
private static ResponseSpecification responseSpec;
private static RequestSpecification requestSpec;
@@ -83,7 +84,6 @@ public class SavingsInterestPostingJobIntegrationTest {
public void testSavingsBalanceCheckAfterDailyInterestPostingJob() {
// client activation, savings activation and 1st transaction date
final String startDate = "10 April 2022";
- final String jobName = "Post Interest For Savings";
final Integer clientID = ClientHelper.createClient(this.requestSpec,
this.responseSpec, startDate);
Assertions.assertNotNull(clientID);
@@ -95,7 +95,7 @@ public class SavingsInterestPostingJobIntegrationTest {
* Runs Post interest posting job and verify the new account created
with accounting configuration set as none
* is picked up by job
*/
- this.scheduleJobHelper.executeAndAwaitJob(jobName);
+
this.scheduleJobHelper.executeAndAwaitJobByShortName(POST_INTEREST_FOR_SAVINGS_JOB_SHORT_NAME);
Object transactionObj =
this.savingsAccountHelper.getSavingsDetails(savingsId, "transactions");
ArrayList<HashMap<String, Object>> transactions =
(ArrayList<HashMap<String, Object>>) transactionObj;
HashMap<String, Object> interestPostingTransaction =
transactions.get(transactions.size() - 48);
@@ -108,7 +108,6 @@ public class SavingsInterestPostingJobIntegrationTest {
@Test
public void testSavingsDailyInterestPostingJobWithAccountingNone() {
final String startDate = "10 April 2022";
- final String jobName = "Post Interest For Savings";
final Integer clientID = ClientHelper.createClient(this.requestSpec,
this.responseSpec, startDate);
Assertions.assertNotNull(clientID);
this.accountHelper = new AccountHelper(requestSpec, responseSpec);
@@ -126,7 +125,6 @@ public class SavingsInterestPostingJobIntegrationTest {
public void testDuplicateOverdraftInterestPostingJob() {
// client activation, savings activation and 1st transaction date
final String startDate = "01 July 2022";
- final String jobName = "Post Interest For Savings";
final Integer clientID = ClientHelper.createClient(this.requestSpec,
this.responseSpec, startDate);
Assertions.assertNotNull(clientID);
@@ -134,7 +132,7 @@ public class SavingsInterestPostingJobIntegrationTest {
this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId,
"1000", startDate, CommonConstants.RESPONSE_RESOURCE_ID);
- this.scheduleJobHelper.executeAndAwaitJob(jobName);
+
this.scheduleJobHelper.executeAndAwaitJobByShortName(POST_INTEREST_FOR_SAVINGS_JOB_SHORT_NAME);
this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId,
"1000", startDate, CommonConstants.RESPONSE_RESOURCE_ID);
Object transactionObj =
this.savingsAccountHelper.getSavingsDetails(savingsId, "transactions");
ArrayList<HashMap<String, Object>> transactions =
(ArrayList<HashMap<String, Object>>) transactionObj;
@@ -155,7 +153,6 @@ public class SavingsInterestPostingJobIntegrationTest {
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec,
BusinessDateType.BUSINESS_DATE, today);
// client activation, savings activation and 1st transaction date
final String startDate = "10 April 2022";
- final String jobName = "Post Interest For Savings";
final Integer clientID =
ClientHelper.createClient(this.requestSpec, this.responseSpec, startDate);
Assertions.assertNotNull(clientID);
@@ -167,7 +164,7 @@ public class SavingsInterestPostingJobIntegrationTest {
* Runs Post interest posting job and verify the new account
created with accounting configuration set as
* none is picked up by job
*/
- this.scheduleJobHelper.executeAndAwaitJob(jobName);
+
this.scheduleJobHelper.executeAndAwaitJobByShortName(POST_INTEREST_FOR_SAVINGS_JOB_SHORT_NAME);
Object transactionObj =
this.savingsAccountHelper.getSavingsDetails(savingsId, "transactions");
ArrayList<HashMap<String, Object>> transactions =
(ArrayList<HashMap<String, Object>>) transactionObj;
HashMap<String, Object> interestPostingTransaction =
transactions.get(transactions.size() - 3);
@@ -190,7 +187,6 @@ public class SavingsInterestPostingJobIntegrationTest {
public void testSavingsDailyOverdraftInterestPostingJob() {
// client activation, savings activation and 1st transaction date
final String startDate = "10 April 2022";
- final String jobName = "Post Interest For Savings";
final Integer clientID = ClientHelper.createClient(this.requestSpec,
this.responseSpec, startDate);
Assertions.assertNotNull(clientID);
@@ -199,7 +195,7 @@ public class SavingsInterestPostingJobIntegrationTest {
this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId,
"10000", startDate, CommonConstants.RESPONSE_RESOURCE_ID);
// Runs Post interest posting job and verify the new account created
with Overdraft is posting negative interest
- this.scheduleJobHelper.executeAndAwaitJob(jobName);
+
this.scheduleJobHelper.executeAndAwaitJobByShortName(POST_INTEREST_FOR_SAVINGS_JOB_SHORT_NAME);
Object transactionObj =
this.savingsAccountHelper.getSavingsDetails(savingsId, "transactions");
ArrayList<HashMap<String, Object>> transactions =
(ArrayList<HashMap<String, Object>>) transactionObj;
HashMap<String, Object> interestPostingTransaction =
transactions.get(transactions.size() - 2);
@@ -215,7 +211,6 @@ public class SavingsInterestPostingJobIntegrationTest {
@Test
public void testAccountBalanceWithWithdrawalFeeAfterInterestPostingJob() {
final String startDate = "21 June 2022";
- final String jobName = "Post Interest For Savings";
final Integer clientID = ClientHelper.createClient(this.requestSpec,
this.responseSpec, startDate);
Assertions.assertNotNull(clientID);
@@ -227,7 +222,7 @@ public class SavingsInterestPostingJobIntegrationTest {
Float balance = Float.parseFloat("800.0");
assertEquals(balance, summary.get("accountBalance"), "Verifying
account balance is 800");
- this.scheduleJobHelper.executeAndAwaitJob(jobName);
+
this.scheduleJobHelper.executeAndAwaitJobByShortName(POST_INTEREST_FOR_SAVINGS_JOB_SHORT_NAME);
Object transactionObj =
this.savingsAccountHelper.getSavingsDetails(savingsId, "transactions");
ArrayList<HashMap<String, Object>> transactions =
(ArrayList<HashMap<String, Object>>) transactionObj;
HashMap<String, Object> interestPostingTransaction =
transactions.get(transactions.size() - 5);
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 8f069ccf1..c343f25b2 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
@@ -19,6 +19,7 @@
package org.apache.fineract.integrationtests.common;
import static java.time.Instant.now;
+import static
org.apache.fineract.infrastructure.jobs.api.SchedulerJobApiConstants.SHORT_NAME_PARAM;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.core.Is.is;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -28,6 +29,7 @@ import com.google.gson.Gson;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
+import java.io.Serializable;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
@@ -37,7 +39,9 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
+import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.fineract.client.models.PutJobsJobIDRequest;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
@@ -89,6 +93,15 @@ public class SchedulerJobHelper extends IntegrationTest {
return response;
}
+ private Map<String, Object> getSchedulerJobByShortName(String shortName) {
+ final String GET_SCHEDULER_JOB_URL = "/fineract-provider/api/v1/jobs/"
+ SHORT_NAME_PARAM + "/" + shortName + "?"
+ + Utils.TENANT_IDENTIFIER;
+ LOG.info("------------------------ RETRIEVING SCHEDULER JOB BY SHORT
NAME -------------------------");
+ Map<String, Object> response = Utils.performServerGet(requestSpec,
response200Spec, GET_SCHEDULER_JOB_URL, "");
+ assertNotNull(response);
+ return response;
+ }
+
public Boolean getSchedulerStatus() {
final String GET_SCHEDULER_STATUS_URL =
"/fineract-provider/api/v1/scheduler?" + Utils.TENANT_IDENTIFIER;
LOG.info("------------------------ RETRIEVING SCHEDULER STATUS
-------------------------");
@@ -131,6 +144,18 @@ public class SchedulerJobHelper extends IntegrationTest {
Utils.performServerPost(requestSpec, responseSpec,
RUN_SCHEDULER_JOB_URL, runSchedulerJobAsJSON(), null);
}
+ public void runSchedulerJobByShortName(String shortName) {
+ final ResponseSpecification responseSpec = new
ResponseSpecBuilder().expectStatusCode(202).build();
+ runSchedulerJobByShortName(shortName, responseSpec);
+ }
+
+ public void runSchedulerJobByShortName(String shortName,
ResponseSpecification responseSpec) {
+ final String RUN_SCHEDULER_JOB_URL = "/fineract-provider/api/v1/jobs/"
+ SHORT_NAME_PARAM + "/" + shortName + "?command=executeJob&"
+ + Utils.TENANT_IDENTIFIER;
+ LOG.info("------------------------ RUN SCHEDULER JOB
-------------------------");
+ Utils.performServerPost(requestSpec, responseSpec,
RUN_SCHEDULER_JOB_URL, runSchedulerJobAsJSON(), null);
+ }
+
private static String runSchedulerJobAsJSON() {
final Map<String, String> map = new HashMap<>();
String runSchedulerJob = new Gson().toJson(map);
@@ -149,6 +174,16 @@ public class SchedulerJobHelper extends IntegrationTest {
"No such named Job (see
org.apache.fineract.infrastructure.jobs.service.JobName enum):" + jobName);
}
+ public int getSchedulerJobIdByShortName(String shortName) {
+ Map<String, Object> jobMap = getSchedulerJobByShortName(shortName);
+ final String GET_SCHEDULER_JOB_URL = "/fineract-provider/api/v1/jobs/"
+ SHORT_NAME_PARAM + "/" + shortName + "?"
+ + Utils.TENANT_IDENTIFIER;
+ LOG.info("------------------------ RETRIEVING SCHEDULER JOB ID BY
SHORT NAME -------------------------");
+ Integer response = (Integer) jobMap.get("jobId");
+ assertNotNull(response);
+ return response;
+ }
+
/**
* Launches a Job and awaits its completion.
*
@@ -158,23 +193,43 @@ public class SchedulerJobHelper extends IntegrationTest {
* @author Michael Vorburger.ch
*/
public void executeAndAwaitJob(String jobName) {
- final Duration timeout = Duration.ofMinutes(4);
- final Duration pause = Duration.ofSeconds(2);
- DateTimeFormatter df = DateTimeFormatter.ISO_INSTANT; // FINERACT-926
- Instant beforeExecuteTime = now().truncatedTo(ChronoUnit.SECONDS);
+ int jobId = getSchedulerJobIdByName(jobName);
+ executeAndAwaitJob(jobId, (a) -> runSchedulerJob(jobId), () ->
jobLastRunHistorySupplier(jobId));
+ }
+ /**
+ * Launches a Job and awaits its completion.
+ *
+ * @param shortName
+ * shortName of Scheduler Job
+ *
+ * @author Michael Vorburger.ch
+ */
+ public void executeAndAwaitJobByShortName(String shortName) {
+ executeAndAwaitJob(shortName, (a) ->
runSchedulerJobByShortName(shortName), () ->
jobLastRunHistoryByShortName(shortName));
+ }
+
+ public <T extends Serializable> void executeAndAwaitJob(T jobParam,
Consumer<T> runSchedulerJob,
+ Supplier<Callable<Map<String, String>>> retrievelastRunHistory) {
// Stop the Scheduler while we manually trigger execution of job, to
// avoid side effects and simplify debugging when readings logs
updateSchedulerStatus(false);
+ Instant beforeExecuteTime = now().truncatedTo(ChronoUnit.SECONDS);
// Executing Scheduler Job
- int jobId = getSchedulerJobIdByName(jobName);
- runSchedulerJob(jobId);
+ runSchedulerJob.accept(jobParam);
+ awaitJob(beforeExecuteTime, retrievelastRunHistory);
+ }
+
+ private void awaitJob(Instant beforeExecuteTime,
Supplier<Callable<Map<String, String>>> retrieveLastRunHistory) {
+ final Duration timeout = Duration.ofMinutes(4);
+ final Duration pause = Duration.ofSeconds(2);
+ DateTimeFormatter df = DateTimeFormatter.ISO_INSTANT; // FINERACT-926
// Await JobDetailData.lastRunHistory [JobDetailHistoryData]
// jobRunStartTime >= beforeExecuteTime (or timeout)
// jobRunEndTime to be both set and >= jobRunStartTime (or timeout)
- Map<String, String> finalLastRunHistory =
await().atMost(timeout).pollInterval(pause).until(jobLastRunHistorySupplier(jobId),
+ Map<String, String> finalLastRunHistory =
await().atMost(timeout).pollInterval(pause).until(retrieveLastRunHistory.get(),
lastRunHistory -> {
String jobRunStartText =
lastRunHistory.get("jobRunStartTime");
if (jobRunStartText == null) {
@@ -223,4 +278,15 @@ public class SchedulerJobHelper extends IntegrationTest {
return (Map<String, String>) job.get("lastRunHistory");
};
}
+
+ @SuppressWarnings("unchecked")
+ private Callable<Map<String, String>> jobLastRunHistoryByShortName(String
shortName) {
+ return () -> {
+ Map<String, Object> job = getSchedulerJobByShortName(shortName);
+ if (job == null) {
+ return null;
+ }
+ return (Map<String, String>) job.get("lastRunHistory");
+ };
+ }
}