You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ar...@apache.org on 2022/11/23 14:51:06 UTC

[fineract] branch develop updated: FINERACT-1678-Extending-jobs-with-module-system

This is an automated email from the ASF dual-hosted git repository.

arnold 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 c1df5e73a FINERACT-1678-Extending-jobs-with-module-system
c1df5e73a is described below

commit c1df5e73a26cc887c70e67723a56fe3e71495800
Author: Ruchi Dhamankar <ru...@gmail.com>
AuthorDate: Tue Nov 15 10:40:29 2022 +0530

    FINERACT-1678-Extending-jobs-with-module-system
---
 custom/acme/loan/cob/dependencies.gradle           |  1 +
 .../{cob/dependencies.gradle => job/build.gradle}  |  9 ++--
 custom/acme/loan/{cob => job}/dependencies.gradle  |  2 +
 .../com/acme/fineract/loan/job/AcmeJobName.java}   | 17 ++++++-
 .../acme/fineract/loan/job/AcmeJobNameConfig.java} | 17 ++++++-
 .../loan/job/AcmeNoopJobConfiguration.java         | 50 ++++++++++++++++++++
 .../fineract/loan/job/AcmeNoopJobTasklet.java}     | 19 +++++++-
 .../db/custom-changelog/0001_acme_loan_job.xml     | 45 ++++++++++++++++++
 .../job/src/test/java/AcmeNoopJobTaskletTest.java  | 53 ++++++++++++++++++++++
 .../loan/starter/AcmeLoanAutoConfiguration.java    |  3 +-
 .../jobs/config/JobNameProviderConfig.java         | 38 ++++++++++++++++
 .../infrastructure/jobs/service/JobName.java       |  9 +---
 .../jobs/service/JobRegisterServiceImpl.java       |  9 +++-
 .../jobs/service/jobname/JobNameData.java          | 12 ++++-
 .../jobs/service/jobname/JobNameProvider.java      |  8 +++-
 .../jobs/service/jobname/JobNameService.java       | 43 ++++++++++++++++++
 .../service/jobname/SimpleJobNameProvider.java     | 18 +++++++-
 17 files changed, 327 insertions(+), 26 deletions(-)

diff --git a/custom/acme/loan/cob/dependencies.gradle b/custom/acme/loan/cob/dependencies.gradle
index 59d668289..364f7d772 100644
--- a/custom/acme/loan/cob/dependencies.gradle
+++ b/custom/acme/loan/cob/dependencies.gradle
@@ -19,4 +19,5 @@
 
 dependencies {
     implementation(project(':fineract-provider'))
+    implementation('org.springframework.boot:spring-boot-starter-data-jpa')
 }
diff --git a/custom/acme/loan/cob/dependencies.gradle b/custom/acme/loan/job/build.gradle
similarity index 83%
copy from custom/acme/loan/cob/dependencies.gradle
copy to custom/acme/loan/job/build.gradle
index 59d668289..235711ad7 100644
--- a/custom/acme/loan/cob/dependencies.gradle
+++ b/custom/acme/loan/job/build.gradle
@@ -16,7 +16,10 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+description = 'ACME Corp.: Fineract Loan Job'
 
-dependencies {
-    implementation(project(':fineract-provider'))
-}
+group = 'com.acme.fineract'
+
+archivesBaseName = 'acme-fineract-loan-job'
+
+apply from: 'dependencies.gradle'
diff --git a/custom/acme/loan/cob/dependencies.gradle b/custom/acme/loan/job/dependencies.gradle
similarity index 85%
copy from custom/acme/loan/cob/dependencies.gradle
copy to custom/acme/loan/job/dependencies.gradle
index 59d668289..fd2f26643 100644
--- a/custom/acme/loan/cob/dependencies.gradle
+++ b/custom/acme/loan/job/dependencies.gradle
@@ -19,4 +19,6 @@
 
 dependencies {
     implementation(project(':fineract-provider'))
+    implementation('org.springframework.batch:spring-batch-integration')
+    implementation('org.springframework.boot:spring-boot-starter-data-jpa')
 }
diff --git a/custom/acme/loan/cob/dependencies.gradle b/custom/acme/loan/job/src/main/java/com/acme/fineract/loan/job/AcmeJobName.java
similarity index 74%
copy from custom/acme/loan/cob/dependencies.gradle
copy to custom/acme/loan/job/src/main/java/com/acme/fineract/loan/job/AcmeJobName.java
index 59d668289..342b9f189 100644
--- a/custom/acme/loan/cob/dependencies.gradle
+++ b/custom/acme/loan/job/src/main/java/com/acme/fineract/loan/job/AcmeJobName.java
@@ -16,7 +16,20 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package com.acme.fineract.loan.job;
 
-dependencies {
-    implementation(project(':fineract-provider'))
+public enum AcmeJobName {
+
+    ACME_NOOP_JOB("Acme Noop Job");
+
+    private final String name;
+
+    AcmeJobName(final String name) {
+        this.name = name;
+    }
+
+    @Override
+    public String toString() {
+        return this.name;
+    }
 }
diff --git a/custom/acme/loan/cob/dependencies.gradle b/custom/acme/loan/job/src/main/java/com/acme/fineract/loan/job/AcmeJobNameConfig.java
similarity index 54%
copy from custom/acme/loan/cob/dependencies.gradle
copy to custom/acme/loan/job/src/main/java/com/acme/fineract/loan/job/AcmeJobNameConfig.java
index 59d668289..4aabfbab4 100644
--- a/custom/acme/loan/cob/dependencies.gradle
+++ b/custom/acme/loan/job/src/main/java/com/acme/fineract/loan/job/AcmeJobNameConfig.java
@@ -16,7 +16,20 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package com.acme.fineract.loan.job;
 
-dependencies {
-    implementation(project(':fineract-provider'))
+import java.util.List;
+import org.apache.fineract.infrastructure.jobs.service.jobname.JobNameData;
+import org.apache.fineract.infrastructure.jobs.service.jobname.JobNameProvider;
+import org.apache.fineract.infrastructure.jobs.service.jobname.SimpleJobNameProvider;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class AcmeJobNameConfig {
+
+    @Bean
+    public JobNameProvider acmeJobNameProvider() {
+        return new SimpleJobNameProvider(List.of(new JobNameData(AcmeJobName.ACME_NOOP_JOB.name(), AcmeJobName.ACME_NOOP_JOB.toString())));
+    }
 }
diff --git a/custom/acme/loan/job/src/main/java/com/acme/fineract/loan/job/AcmeNoopJobConfiguration.java b/custom/acme/loan/job/src/main/java/com/acme/fineract/loan/job/AcmeNoopJobConfiguration.java
new file mode 100644
index 000000000..040c9e9b2
--- /dev/null
+++ b/custom/acme/loan/job/src/main/java/com/acme/fineract/loan/job/AcmeNoopJobConfiguration.java
@@ -0,0 +1,50 @@
+/**
+ * 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 com.acme.fineract.loan.job;
+
+import org.springframework.batch.core.Job;
+import org.springframework.batch.core.Step;
+import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
+import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
+import org.springframework.batch.core.launch.support.RunIdIncrementer;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class AcmeNoopJobConfiguration {
+
+    @Autowired
+    private JobBuilderFactory jobs;
+    @Autowired
+    private StepBuilderFactory steps;
+    @Autowired
+    private AcmeNoopJobTasklet tasklet;
+
+    @Bean
+    protected Step acmeNoopJobStep() {
+        return steps.get(AcmeJobName.ACME_NOOP_JOB.name()).tasklet(tasklet).build();
+    }
+
+    @Bean
+    public Job acmeNoopJob() {
+        return jobs.get(AcmeJobName.ACME_NOOP_JOB.name()).start(acmeNoopJobStep()).incrementer(new RunIdIncrementer()).build();
+    }
+
+}
diff --git a/custom/acme/loan/cob/dependencies.gradle b/custom/acme/loan/job/src/main/java/com/acme/fineract/loan/job/AcmeNoopJobTasklet.java
similarity index 55%
copy from custom/acme/loan/cob/dependencies.gradle
copy to custom/acme/loan/job/src/main/java/com/acme/fineract/loan/job/AcmeNoopJobTasklet.java
index 59d668289..4c644e1ce 100644
--- a/custom/acme/loan/cob/dependencies.gradle
+++ b/custom/acme/loan/job/src/main/java/com/acme/fineract/loan/job/AcmeNoopJobTasklet.java
@@ -16,7 +16,22 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package com.acme.fineract.loan.job;
 
-dependencies {
-    implementation(project(':fineract-provider'))
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.batch.core.StepContribution;
+import org.springframework.batch.core.scope.context.ChunkContext;
+import org.springframework.batch.core.step.tasklet.Tasklet;
+import org.springframework.batch.repeat.RepeatStatus;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+public class AcmeNoopJobTasklet implements Tasklet {
+
+    @Override
+    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
+        log.info("Acme custom job execution");
+        return RepeatStatus.FINISHED;
+    }
 }
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
new file mode 100644
index 000000000..f466fdaf3
--- /dev/null
+++ b/custom/acme/loan/job/src/main/resources/db/custom-changelog/0001_acme_loan_job.xml
@@ -0,0 +1,45 @@
+<?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="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>
+</databaseChangeLog>
diff --git a/custom/acme/loan/job/src/test/java/AcmeNoopJobTaskletTest.java b/custom/acme/loan/job/src/test/java/AcmeNoopJobTaskletTest.java
new file mode 100644
index 000000000..f8b6e5026
--- /dev/null
+++ b/custom/acme/loan/job/src/test/java/AcmeNoopJobTaskletTest.java
@@ -0,0 +1,53 @@
+
+/**
+ * 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.
+ */
+import static org.junit.Assert.assertEquals;
+
+import com.acme.fineract.loan.job.AcmeNoopJobTasklet;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.batch.core.StepContribution;
+import org.springframework.batch.core.scope.context.ChunkContext;
+import org.springframework.batch.repeat.RepeatStatus;
+
+@ExtendWith(MockitoExtension.class)
+public class AcmeNoopJobTaskletTest {
+
+    @Mock
+    private StepContribution stepContribution;
+    @Mock
+    private ChunkContext chunkContext;
+    private RepeatStatus resultStatus;
+    private AcmeNoopJobTasklet underTest;
+
+    @BeforeEach
+    public void setUp() {
+        underTest = new AcmeNoopJobTasklet();
+    }
+
+    @Test
+    public void testJobExecution() throws Exception {
+        resultStatus = underTest.execute(stepContribution, chunkContext);
+        assertEquals(resultStatus, RepeatStatus.FINISHED);
+    }
+
+}
diff --git a/custom/acme/loan/starter/src/main/java/com/acme/fineract/loan/starter/AcmeLoanAutoConfiguration.java b/custom/acme/loan/starter/src/main/java/com/acme/fineract/loan/starter/AcmeLoanAutoConfiguration.java
index 8e4560a32..d1516f3a0 100644
--- a/custom/acme/loan/starter/src/main/java/com/acme/fineract/loan/starter/AcmeLoanAutoConfiguration.java
+++ b/custom/acme/loan/starter/src/main/java/com/acme/fineract/loan/starter/AcmeLoanAutoConfiguration.java
@@ -29,7 +29,8 @@ import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.ComponentScans;
 
 @AutoConfiguration
-@ComponentScans({ @ComponentScan("com.acme.fineract.loan.cob"), @ComponentScan("com.acme.fineract.loan.processor") })
+@ComponentScans({ @ComponentScan("com.acme.fineract.loan.cob"), @ComponentScan("com.acme.fineract.loan.processor"),
+        @ComponentScan("com.acme.fineract.loan.job") })
 @ConditionalOnProperty("acme.loan.enabled")
 public class AcmeLoanAutoConfiguration {
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/config/JobNameProviderConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/config/JobNameProviderConfig.java
new file mode 100644
index 000000000..a43bc54c3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/config/JobNameProviderConfig.java
@@ -0,0 +1,38 @@
+/**
+ * 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.config;
+
+import java.util.Arrays;
+import java.util.List;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.apache.fineract.infrastructure.jobs.service.jobname.JobNameData;
+import org.apache.fineract.infrastructure.jobs.service.jobname.JobNameProvider;
+import org.apache.fineract.infrastructure.jobs.service.jobname.SimpleJobNameProvider;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class JobNameProviderConfig {
+
+    @Bean
+    public JobNameProvider fineractJobNameProvider() {
+        List<JobNameData> jobNames = Arrays.stream(JobName.values()).map(jn -> new JobNameData(jn.name(), jn.toString())).toList();
+        return new SimpleJobNameProvider(jobNames);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
index 9b1a32308..1858733e4 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
@@ -18,9 +18,6 @@
  */
 package org.apache.fineract.infrastructure.jobs.service;
 
-import java.util.Arrays;
-import java.util.Optional;
-
 public enum JobName {
 
     UPDATE_LOAN_ARREARS_AGEING("Update Loan Arrears Ageing"), //
@@ -66,13 +63,9 @@ public enum JobName {
         this.name = name;
     }
 
-    public static JobName getJobByName(String jobName) {
-        Optional<JobName> optionalJob = Arrays.stream(JobName.values()).filter(jn -> jobName.equals(jn.name)).findAny();
-        return optionalJob.orElseThrow(() -> new IllegalArgumentException("Job not found by name: " + jobName));
-    }
-
     @Override
     public String toString() {
         return this.name;
     }
+
 }
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 bd627cc92..d5ae619f8 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
@@ -33,6 +33,8 @@ import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail;
 import org.apache.fineract.infrastructure.jobs.domain.SchedulerDetail;
 import org.apache.fineract.infrastructure.jobs.exception.JobNodeIdMismatchingException;
 import org.apache.fineract.infrastructure.jobs.exception.JobNotFoundException;
+import org.apache.fineract.infrastructure.jobs.service.jobname.JobNameData;
+import org.apache.fineract.infrastructure.jobs.service.jobname.JobNameService;
 import org.quartz.JobDataMap;
 import org.quartz.JobDetail;
 import org.quartz.JobKey;
@@ -79,6 +81,9 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
     @Autowired
     private JobStarter jobStarter;
 
+    @Autowired
+    private JobNameService jobNameService;
+
     private static final String JOB_STARTER_METHOD_NAME = "run";
 
     public void executeJob(final ScheduledJobDetail scheduledJobDetail, String triggerType) {
@@ -313,8 +318,8 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
     private JobDetail createJobDetail(final ScheduledJobDetail scheduledJobDetail) throws Exception {
         final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
 
-        JobName jobName = JobName.getJobByName(scheduledJobDetail.getJobName());
-        Job job = jobLocator.getJob(jobName.name());
+        JobNameData jobName = jobNameService.getJobByHumanReadableName(scheduledJobDetail.getJobName());
+        Job job = jobLocator.getJob(jobName.getEnumStyleName());
 
         final MethodInvokingJobDetailFactoryBean jobDetailFactoryBean = new MethodInvokingJobDetailFactoryBean();
         jobDetailFactoryBean.setName(scheduledJobDetail.getJobName() + "JobDetail" + tenant.getId());
diff --git a/custom/acme/loan/cob/dependencies.gradle b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobname/JobNameData.java
similarity index 74%
copy from custom/acme/loan/cob/dependencies.gradle
copy to fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobname/JobNameData.java
index 59d668289..dda306214 100644
--- a/custom/acme/loan/cob/dependencies.gradle
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobname/JobNameData.java
@@ -16,7 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.fineract.infrastructure.jobs.service.jobname;
 
-dependencies {
-    implementation(project(':fineract-provider'))
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor
+public class JobNameData {
+
+    private final String enumStyleName;
+    private final String humanReadableName;
 }
diff --git a/custom/acme/loan/cob/dependencies.gradle b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobname/JobNameProvider.java
similarity index 83%
copy from custom/acme/loan/cob/dependencies.gradle
copy to fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobname/JobNameProvider.java
index 59d668289..9f1a17db7 100644
--- a/custom/acme/loan/cob/dependencies.gradle
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobname/JobNameProvider.java
@@ -16,7 +16,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.fineract.infrastructure.jobs.service.jobname;
 
-dependencies {
-    implementation(project(':fineract-provider'))
+import java.util.Set;
+
+public interface JobNameProvider {
+
+    Set<JobNameData> provide();
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobname/JobNameService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobname/JobNameService.java
new file mode 100644
index 000000000..b0feb804d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobname/JobNameService.java
@@ -0,0 +1,43 @@
+/**
+ * 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.service.jobname;
+
+import static java.util.stream.Collectors.toSet;
+
+import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class JobNameService {
+
+    private final Collection<JobNameProvider> providers;
+
+    public JobNameData getJobByHumanReadableName(String jobName) {
+        Optional<JobNameData> optionalJob = getJobNames().stream().filter(jn -> jobName.equals(jn.getHumanReadableName())).findAny();
+        return optionalJob.orElseThrow(() -> new IllegalArgumentException("Job not found by name: " + jobName));
+    }
+
+    private Set<JobNameData> getJobNames() {
+        return providers.stream().map(JobNameProvider::provide).flatMap(Collection::stream).collect(toSet());
+    }
+}
diff --git a/custom/acme/loan/cob/dependencies.gradle b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobname/SimpleJobNameProvider.java
similarity index 64%
copy from custom/acme/loan/cob/dependencies.gradle
copy to fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobname/SimpleJobNameProvider.java
index 59d668289..7c03d0062 100644
--- a/custom/acme/loan/cob/dependencies.gradle
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobname/SimpleJobNameProvider.java
@@ -16,7 +16,21 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.fineract.infrastructure.jobs.service.jobname;
 
-dependencies {
-    implementation(project(':fineract-provider'))
+import java.util.Collection;
+import java.util.Set;
+
+public class SimpleJobNameProvider implements JobNameProvider {
+
+    private final Set<JobNameData> jobNames;
+
+    public SimpleJobNameProvider(Collection<JobNameData> jobNames) {
+        this.jobNames = Set.copyOf(jobNames);
+    }
+
+    @Override
+    public Set<JobNameData> provide() {
+        return jobNames;
+    }
 }