From 774a615bb20e429231d17df51feadf6ccf065721 Mon Sep 17 00:00:00 2001 From: himeshr Date: Tue, 16 Jul 2024 17:45:34 +0530 Subject: [PATCH] #102 | Override all ETL Job endpoints to support 2 different jobGroups for each organisation: Sync and MediaAnalysis --- .../etl/config/ScheduledJobConfig.java | 22 +++--- .../etl/contract/JobScheduleRequest.java | 10 +++ .../etl/contract/backgroundJob/JobGroup.java | 20 ++++++ .../backgroundJob/EtlJobController.java | 70 ++++++++----------- .../etl/scheduler/MediaAnalysisJob.java | 30 ++++++++ .../etl/service/MediaAnalysisService.java | 57 +++++++++++++++ .../backgroundJob/ScheduledJobService.java | 32 +++++---- .../resources/main-application.properties | 3 +- 8 files changed, 183 insertions(+), 61 deletions(-) create mode 100644 src/main/java/org/avniproject/etl/contract/backgroundJob/JobGroup.java create mode 100644 src/main/java/org/avniproject/etl/scheduler/MediaAnalysisJob.java create mode 100644 src/main/java/org/avniproject/etl/service/MediaAnalysisService.java diff --git a/src/main/java/org/avniproject/etl/config/ScheduledJobConfig.java b/src/main/java/org/avniproject/etl/config/ScheduledJobConfig.java index e8dfc7b..cd987e9 100644 --- a/src/main/java/org/avniproject/etl/config/ScheduledJobConfig.java +++ b/src/main/java/org/avniproject/etl/config/ScheduledJobConfig.java @@ -1,6 +1,7 @@ package org.avniproject.etl.config; import org.avniproject.etl.contract.backgroundJob.JobEntityType; +import org.avniproject.etl.contract.backgroundJob.JobGroup; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.JobKey; @@ -12,24 +13,29 @@ @Component public class ScheduledJobConfig { - public static final String SYNC_JOB_GROUP = "SyncJobs"; - public static final String SYNC_TRIGGER_GROUP = "SyncTriggers"; public static final String JOB_CREATED_AT = "CreatedAt"; public static final String ENTITY_TYPE = "EntityType"; - @Value("${avni.scheduledJob.repeatIntervalInMinutes}") + @Value("${avni.scheduledJob.sync.repeatIntervalInMinutes}") private int repeatIntervalInMinutes; - public TriggerKey getTriggerKey(String organisationUUID) { - return new TriggerKey(organisationUUID, SYNC_TRIGGER_GROUP); + @Value("${avni.scheduledJob.mediaAnalysis.repeatIntervalInMinutes}") + private int mediaAnalysisRepeatIntervalInMinutes; + + public TriggerKey getTriggerKey(String organisationUUID, JobGroup jobGroup) { + return new TriggerKey(organisationUUID, jobGroup.getTriggerName()); } - public int getRepeatIntervalInMinutes() { + public int getSyncRepeatIntervalInMinutes() { return repeatIntervalInMinutes; } - public JobKey getJobKey(String organisationUUID) { - return new JobKey(organisationUUID, SYNC_JOB_GROUP); + public int getMediaAnalysisRepeatIntervalInMinutes() { + return mediaAnalysisRepeatIntervalInMinutes; + } + + public JobKey getJobKey(String organisationUUID, JobGroup jobGroup) { + return new JobKey(organisationUUID, jobGroup.getGroupName()); } public String getEntityId(JobDetail jobDetail) { diff --git a/src/main/java/org/avniproject/etl/contract/JobScheduleRequest.java b/src/main/java/org/avniproject/etl/contract/JobScheduleRequest.java index 1e35382..c678fb0 100644 --- a/src/main/java/org/avniproject/etl/contract/JobScheduleRequest.java +++ b/src/main/java/org/avniproject/etl/contract/JobScheduleRequest.java @@ -1,10 +1,12 @@ package org.avniproject.etl.contract; import org.avniproject.etl.contract.backgroundJob.JobEntityType; +import org.avniproject.etl.contract.backgroundJob.JobGroup; public class JobScheduleRequest { private String entityUUID; private JobEntityType jobEntityType; + private JobGroup jobGroup = JobGroup.Sync; public String getEntityUUID() { return entityUUID; @@ -21,4 +23,12 @@ public JobEntityType getJobEntityType() { public void setJobEntityType(JobEntityType jobEntityType) { this.jobEntityType = jobEntityType; } + + public JobGroup getJobGroup() { + return jobGroup; + } + + public void setJobGroup(JobGroup jobGroup) { + this.jobGroup = jobGroup; + } } diff --git a/src/main/java/org/avniproject/etl/contract/backgroundJob/JobGroup.java b/src/main/java/org/avniproject/etl/contract/backgroundJob/JobGroup.java new file mode 100644 index 0000000..f202293 --- /dev/null +++ b/src/main/java/org/avniproject/etl/contract/backgroundJob/JobGroup.java @@ -0,0 +1,20 @@ +package org.avniproject.etl.contract.backgroundJob; + +public enum JobGroup { + Sync("SyncJobs", "SyncTriggers"), MediaAnalysis("MediaAnalysisJobs", "MediaAnalysisTriggers"); + + String groupName, triggerName; + + JobGroup(String groupName, String triggerName) { + this.groupName = groupName; + this.triggerName = triggerName; + } + + public String getGroupName() { + return groupName; + } + + public String getTriggerName() { + return triggerName; + } +} diff --git a/src/main/java/org/avniproject/etl/controller/backgroundJob/EtlJobController.java b/src/main/java/org/avniproject/etl/controller/backgroundJob/EtlJobController.java index b860358..284c05e 100644 --- a/src/main/java/org/avniproject/etl/controller/backgroundJob/EtlJobController.java +++ b/src/main/java/org/avniproject/etl/controller/backgroundJob/EtlJobController.java @@ -3,20 +3,14 @@ import org.apache.log4j.Logger; import org.avniproject.etl.config.ScheduledJobConfig; import org.avniproject.etl.contract.JobScheduleRequest; -import org.avniproject.etl.contract.backgroundJob.EtlJobHistoryItem; -import org.avniproject.etl.contract.backgroundJob.EtlJobStatus; -import org.avniproject.etl.contract.backgroundJob.EtlJobSummary; -import org.avniproject.etl.contract.backgroundJob.JobEntityType; +import org.avniproject.etl.contract.backgroundJob.*; import org.avniproject.etl.domain.OrganisationIdentity; import org.avniproject.etl.repository.OrganisationRepository; import org.avniproject.etl.scheduler.EtlJob; +import org.avniproject.etl.scheduler.MediaAnalysisJob; import org.avniproject.etl.service.backgroundJob.ScheduledJobService; import org.avniproject.etl.util.DateTimeUtil; -import org.quartz.JobDataMap; -import org.quartz.Scheduler; -import org.quartz.SchedulerException; -import org.quartz.SimpleScheduleBuilder; -import org.quartz.Trigger; +import org.quartz.*; import org.quartz.impl.JobDetailImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; @@ -28,7 +22,6 @@ import java.util.List; import java.util.stream.Collectors; -import static org.avniproject.etl.config.ScheduledJobConfig.SYNC_JOB_GROUP; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; @@ -50,23 +43,22 @@ public EtlJobController(Scheduler scheduler, ScheduledJobConfig scheduledJobConf @PreAuthorize("hasAnyAuthority('admin')") @GetMapping("/job/{entityUUID}") - public ResponseEntity getJob(@PathVariable String entityUUID) throws SchedulerException { - EtlJobSummary latestJobRun = scheduledJobService.getLatestJobRun(entityUUID); - if (latestJobRun == null) - return ResponseEntity.notFound().build(); + public ResponseEntity getJob(@PathVariable String entityUUID, @RequestParam(value="jobGroup", required = false) JobGroup jobGroup) throws SchedulerException { + EtlJobSummary latestJobRun = scheduledJobService.getLatestJobRun(entityUUID, jobGroup != null ? jobGroup : JobGroup.Sync); + if (latestJobRun == null) return ResponseEntity.notFound().build(); return new ResponseEntity(latestJobRun, HttpStatus.OK); } @PreAuthorize("hasAnyAuthority('admin')") @PostMapping("/job/status") - public List getStatuses(@RequestBody List organisationUUIDs) { - return scheduledJobService.getJobs(organisationUUIDs); + public List getStatuses(@RequestBody List organisationUUIDs, @RequestParam(value="jobGroup", required = false) JobGroup jobGroup) { + return scheduledJobService.getJobs(organisationUUIDs, jobGroup != null ? jobGroup : JobGroup.Sync); } @PreAuthorize("hasAnyAuthority('admin')") @GetMapping("/job/history/{entityUUID}") - public List getJobHistory(@PathVariable String entityUUID) { - return scheduledJobService.getJobHistory(entityUUID); + public List getJobHistory(@PathVariable String entityUUID, @RequestParam(value="jobGroup", required = false) JobGroup jobGroup){ + return scheduledJobService.getJobHistory(entityUUID, jobGroup != null ? jobGroup : JobGroup.Sync); } @PreAuthorize("hasAnyAuthority('admin')") @@ -82,39 +74,31 @@ public ResponseEntity createJob(@RequestBody JobScheduleRequest jobScheduleReque if (organisationIdentity == null && organisationIdentitiesInGroup.size() == 0) return ResponseEntity.badRequest().body(String.format("No such organisation or group exists: %s", jobScheduleRequest.getEntityUUID())); - EtlJobSummary latestJobRun = scheduledJobService.getLatestJobRun(jobScheduleRequest.getEntityUUID()); - if (latestJobRun != null) - return ResponseEntity.badRequest().body("Job already present"); + EtlJobSummary latestJobRun = scheduledJobService.getLatestJobRun(jobScheduleRequest.getEntityUUID(), jobScheduleRequest.getJobGroup()); + if (latestJobRun != null) return ResponseEntity.badRequest().body("Job already present"); JobDetailImpl jobDetail = getJobDetail(jobScheduleRequest, organisationIdentity, organisationIdentitiesInGroup); scheduler.addJob(jobDetail, false); Trigger trigger = getTrigger(jobScheduleRequest, jobDetail); scheduler.scheduleJob(trigger); - logger.info(String.format("Job Scheduled for %s:%s", jobScheduleRequest.getJobEntityType(), jobScheduleRequest.getEntityUUID())); + logger.info(String.format("%s type job Scheduled for %s:%s", jobScheduleRequest.getJobGroup(), jobScheduleRequest.getJobEntityType(), jobScheduleRequest.getEntityUUID())); return ResponseEntity.ok().body("Job Scheduled!"); } private Trigger getTrigger(JobScheduleRequest jobScheduleRequest, JobDetailImpl jobDetail) { - SimpleScheduleBuilder scheduleBuilder = simpleSchedule() - .withIntervalInMinutes(scheduledJobConfig.getRepeatIntervalInMinutes()).repeatForever(); - - Trigger trigger = newTrigger() - .withIdentity(scheduledJobConfig.getTriggerKey(jobScheduleRequest.getEntityUUID())) - .forJob(jobDetail) - .withSchedule(scheduleBuilder) - .startAt(DateTimeUtil.nowPlusSeconds(5)) - .build(); + SimpleScheduleBuilder scheduleBuilder = simpleSchedule().withIntervalInMinutes(jobScheduleRequest.getJobGroup().equals(JobGroup.Sync) ? scheduledJobConfig.getSyncRepeatIntervalInMinutes() : scheduledJobConfig.getMediaAnalysisRepeatIntervalInMinutes()).repeatForever(); + + Trigger trigger = newTrigger().withIdentity(scheduledJobConfig.getTriggerKey(jobScheduleRequest.getEntityUUID(), jobScheduleRequest.getJobGroup())).forJob(jobDetail).withSchedule(scheduleBuilder).startAt(DateTimeUtil.nowPlusSeconds(5)).build(); return trigger; } private JobDetailImpl getJobDetail(JobScheduleRequest jobScheduleRequest, OrganisationIdentity organisationIdentity, List organisationIdentitiesInGroup) { JobDetailImpl jobDetail = new JobDetailImpl(); - jobDetail.setJobClass(EtlJob.class); + jobDetail.setJobClass(jobScheduleRequest.getJobGroup().equals(JobGroup.Sync) ? EtlJob.class : MediaAnalysisJob.class); jobDetail.setDurability(true); - jobDetail.setKey(scheduledJobConfig.getJobKey(jobScheduleRequest.getEntityUUID())); - jobDetail.setDescription(organisationIdentity == null ? - organisationIdentitiesInGroup.stream().map(OrganisationIdentity::toString).collect(Collectors.joining(";")) : organisationIdentity.toString()); - jobDetail.setGroup(SYNC_JOB_GROUP); + jobDetail.setKey(scheduledJobConfig.getJobKey(jobScheduleRequest.getEntityUUID(), jobScheduleRequest.getJobGroup())); + jobDetail.setDescription(organisationIdentity == null ? organisationIdentitiesInGroup.stream().map(OrganisationIdentity::toString).collect(Collectors.joining(";")) : organisationIdentity.toString()); + jobDetail.setGroup(jobScheduleRequest.getJobGroup().getGroupName()); jobDetail.setName(jobScheduleRequest.getEntityUUID()); JobDataMap jobDataMap = scheduledJobConfig.createJobData(jobScheduleRequest.getJobEntityType()); jobDetail.setJobDataMap(jobDataMap); @@ -123,8 +107,16 @@ private JobDetailImpl getJobDetail(JobScheduleRequest jobScheduleRequest, Organi @PreAuthorize("hasAnyAuthority('admin')") @DeleteMapping(value = "/job/{id}") - public String deleteJob(@PathVariable String id) throws SchedulerException { - boolean jobDeleted = scheduler.deleteJob(scheduledJobConfig.getJobKey(id)); - return jobDeleted ? "Job Deleted" : "Job Not Deleted"; + public String deleteJob(@PathVariable String id, @RequestParam(value="jobGroup", required = false) JobGroup jobGroup) throws SchedulerException { + boolean syncJobDeleted = scheduler.deleteJob(scheduledJobConfig.getJobKey(id, jobGroup != null ? jobGroup : JobGroup.Sync)); + String responseMsg = String.format("Sync Job Deleted: %s; ",syncJobDeleted); + if (jobGroup != null && jobGroup == JobGroup.Sync) { + EtlJobSummary mediaJobRun = scheduledJobService.getLatestJobRun(id, JobGroup.MediaAnalysis); + if (mediaJobRun != null) { + boolean mediaAnalysisJobDeleted = scheduler.deleteJob(scheduledJobConfig.getJobKey(id, JobGroup.MediaAnalysis)); + responseMsg.concat(String.format("MediaAnalysis Job Deleted: %s;", mediaAnalysisJobDeleted)); + } + } + return responseMsg; } } diff --git a/src/main/java/org/avniproject/etl/scheduler/MediaAnalysisJob.java b/src/main/java/org/avniproject/etl/scheduler/MediaAnalysisJob.java new file mode 100644 index 0000000..f7dcf98 --- /dev/null +++ b/src/main/java/org/avniproject/etl/scheduler/MediaAnalysisJob.java @@ -0,0 +1,30 @@ +package org.avniproject.etl.scheduler; + +import org.avniproject.etl.config.ScheduledJobConfig; +import org.avniproject.etl.contract.backgroundJob.JobEntityType; +import org.avniproject.etl.service.MediaAnalysisService; +import org.quartz.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class MediaAnalysisJob implements Job { + private final MediaAnalysisService mediaAnalysisService; + private final ScheduledJobConfig scheduledJobConfig; + + @Autowired + public MediaAnalysisJob(MediaAnalysisService mediaAnalysisService, ScheduledJobConfig scheduledJobConfig) { + this.mediaAnalysisService = mediaAnalysisService; + this.scheduledJobConfig = scheduledJobConfig; + } + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException { + JobDetail jobDetail = context.getJobDetail(); + JobDataMap jobDataMap = jobDetail.getJobDataMap(); + String entityId = scheduledJobConfig.getEntityId(jobDetail); + if (jobDataMap.get(ScheduledJobConfig.ENTITY_TYPE).equals(JobEntityType.Organisation)) + mediaAnalysisService.runFor(entityId); + else mediaAnalysisService.runForOrganisationGroup(entityId); + } +} diff --git a/src/main/java/org/avniproject/etl/service/MediaAnalysisService.java b/src/main/java/org/avniproject/etl/service/MediaAnalysisService.java new file mode 100644 index 0000000..5089bec --- /dev/null +++ b/src/main/java/org/avniproject/etl/service/MediaAnalysisService.java @@ -0,0 +1,57 @@ +package org.avniproject.etl.service; + +import org.apache.log4j.Logger; +import org.avniproject.etl.config.EtlServiceConfig; +import org.avniproject.etl.domain.OrgIdentityContextHolder; +import org.avniproject.etl.domain.Organisation; +import org.avniproject.etl.domain.OrganisationIdentity; +import org.avniproject.etl.repository.OrganisationRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class MediaAnalysisService { + private final OrganisationRepository organisationRepository; + private final OrganisationFactory organisationFactory; + private final SchemaMigrationService schemaMigrationService; + private final SyncService syncService; + private final EtlServiceConfig etlServiceConfig; + private static final Logger log = Logger.getLogger(MediaAnalysisService.class); + + @Autowired + public MediaAnalysisService(OrganisationRepository organisationRepository, OrganisationFactory organisationFactory, SchemaMigrationService schemaMigrationService, SyncService syncService, EtlServiceConfig etlServiceConfig) { + this.organisationRepository = organisationRepository; + this.organisationFactory = organisationFactory; + this.schemaMigrationService = schemaMigrationService; + this.syncService = syncService; + this.etlServiceConfig = etlServiceConfig; + } + + public void runFor(String organisationUUID) { + OrganisationIdentity organisationIdentity = organisationRepository.getOrganisation(organisationUUID); + this.runFor(organisationIdentity); + } + + public void runForOrganisationGroup(String organisationGroupUUID) { + List organisationIdentities = organisationRepository.getOrganisationGroup(organisationGroupUUID); + this.runFor(organisationIdentities); + } + + public void runFor(List organisationIdentities) { + organisationIdentities.forEach(this::runFor); + } + + public void runFor(OrganisationIdentity organisationIdentity) { + log.info(String.format("Running Media Analysis for %s", organisationIdentity.toString())); + OrgIdentityContextHolder.setContext(organisationIdentity, etlServiceConfig); + Organisation organisation = organisationFactory.create(organisationIdentity); + log.info(String.format("Old organisation schema summary %s", organisation.getSchemaMetadata().getCountsByType())); + Organisation newOrganisation = schemaMigrationService.migrate(organisation); + log.info(String.format("New organisation after migration, schema summary %s", newOrganisation.getSchemaMetadata().getCountsByType())); +// TODO + log.info(String.format("Completed Media Analysis for schema %s with dbUser %s and schemaUser %s", organisationIdentity.getSchemaName(), organisationIdentity.getDbUser(), organisationIdentity.getSchemaUser())); + OrgIdentityContextHolder.setContext(organisationIdentity, etlServiceConfig); + } +} diff --git a/src/main/java/org/avniproject/etl/service/backgroundJob/ScheduledJobService.java b/src/main/java/org/avniproject/etl/service/backgroundJob/ScheduledJobService.java index df4de77..4a8314a 100644 --- a/src/main/java/org/avniproject/etl/service/backgroundJob/ScheduledJobService.java +++ b/src/main/java/org/avniproject/etl/service/backgroundJob/ScheduledJobService.java @@ -4,6 +4,7 @@ import org.avniproject.etl.contract.backgroundJob.EtlJobHistoryItem; import org.avniproject.etl.contract.backgroundJob.EtlJobStatus; import org.avniproject.etl.contract.backgroundJob.EtlJobSummary; +import org.avniproject.etl.contract.backgroundJob.JobGroup; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerException; @@ -23,13 +24,9 @@ public class ScheduledJobService { private final Scheduler scheduler; private final ScheduledJobConfig scheduledJobConfig; - private static final String HISTORY_QUERY = "select sjr.started_at, sjr.ended_at, sjr.error_message, sjr.success from qrtz_job_details qjd\n" + - " left outer join scheduled_job_run sjr on sjr.job_name = qjd.job_name\n" + - " where sjr.job_name = ?" + - "order by 1 desc\n"; + private static final String HISTORY_QUERY = "select sjr.started_at, sjr.ended_at, sjr.error_message, sjr.success from qrtz_job_details qjd\n" + " left outer join scheduled_job_run sjr on sjr.job_name = qjd.job_name\n" + " where qjd.job_name = ? and qjd.job_group = ?" + "order by 1 desc\n"; - private static final String JOB_LIST_QUERY = "select organisationUUID, job_name from (SELECT unnest(string_to_array(?, ',')) as organisationUUID) foo\n" + - " left outer join qrtz_job_details qjd on organisationUUID = qjd.job_name"; + private static final String JOB_LIST_QUERY = "select organisationUUID, job_name from (SELECT unnest(string_to_array(?, ',')) as organisationUUID) foo\n" + " left outer join qrtz_job_details qjd on organisationUUID = qjd.job_name where qjd.job_group = ?"; @Autowired public ScheduledJobService(JdbcTemplate jdbcTemplate, Scheduler scheduler, ScheduledJobConfig scheduledJobConfig) { @@ -38,24 +35,33 @@ public ScheduledJobService(JdbcTemplate jdbcTemplate, Scheduler scheduler, Sched this.scheduledJobConfig = scheduledJobConfig; } - public List getJobs(List organisationUUIDs) { + public List getJobs(List organisationUUIDs, JobGroup jobGroup) { String organisations = String.join(",", organisationUUIDs); - return jdbcTemplate.query(JOB_LIST_QUERY, ps -> ps.setString(1, organisations), new EtlJobStatusMapper()); + return jdbcTemplate.query(JOB_LIST_QUERY, ps -> { + ps.setString(1, organisations); + ps.setString(2, jobGroup.getGroupName()); + }, new EtlJobStatusMapper()); } - public EtlJobSummary getLatestJobRun(String organisationUUID) throws SchedulerException { + public EtlJobSummary getLatestJobRun(String organisationUUID, JobGroup jobGroup) throws SchedulerException { String query = HISTORY_QUERY + "limit 1"; - List summaries = jdbcTemplate.query(query, ps -> ps.setString(1, organisationUUID), new EtlJobLatestStatusResponseMapper()); + List summaries = jdbcTemplate.query(query, ps -> { + ps.setString(1, organisationUUID); + ps.setString(2, jobGroup.getGroupName()); + }, new EtlJobLatestStatusResponseMapper()); if (summaries.size() == 0) return null; EtlJobSummary etlJobSummary = summaries.get(0); - JobDetail jobDetail = scheduler.getJobDetail(scheduledJobConfig.getJobKey(organisationUUID)); + JobDetail jobDetail = scheduler.getJobDetail(scheduledJobConfig.getJobKey(organisationUUID, jobGroup)); etlJobSummary.setCreatedAt((Date) jobDetail.getJobDataMap().get(ScheduledJobConfig.JOB_CREATED_AT)); return etlJobSummary; } - public List getJobHistory(String organisationUUID) { - return jdbcTemplate.query(HISTORY_QUERY, ps -> ps.setString(1, organisationUUID), new EtlJobHistoryItemMapper()); + public List getJobHistory(String organisationUUID, JobGroup jobGroup) { + return jdbcTemplate.query(HISTORY_QUERY, ps -> { + ps.setString(1, organisationUUID); + ps.setString(2, jobGroup.getGroupName()); + }, new EtlJobHistoryItemMapper()); } static class EtlJobLatestStatusResponseMapper implements RowMapper { diff --git a/src/main/resources/main-application.properties b/src/main/resources/main-application.properties index bfd5f48..f8d726c 100644 --- a/src/main/resources/main-application.properties +++ b/src/main/resources/main-application.properties @@ -16,7 +16,8 @@ spring.quartz.properties.org.quartz.threadPool.threadCount=${ETL_JOB_THREAD_COUN spring.quartz.properties.org.quartz.jobStore.misfireThreshold = ${AVNI_SCHEDULED_JOB_TRIGGER_MISFIRE_THRESHOLD_IN_MILLISECONDS:3600000} # Internal Scheduler config -avni.scheduledJob.repeatIntervalInMinutes=${AVNI_SCHEDULED_JOB_REPEAT_INTERVAL_IN_MINUTES:90} +avni.scheduledJob.sync.repeatIntervalInMinutes=${AVNI_SCHEDULED_JOB_REPEAT_INTERVAL_IN_MINUTES:90} +avni.scheduledJob.mediaAnalysis.repeatIntervalInMinutes=${AVNI_MEDIA_ANALYSIS_JOB_REPEAT_INTERVAL_IN_MINUTES:2} #S3 Parameters avni.bucket.name=${OPENCHS_BUCKET_NAME:dummy}