Skip to content

Commit

Permalink
HTM-1229: introduce a dummy job that is scheduled and add it to the t…
Browse files Browse the repository at this point in the history
…estdata
  • Loading branch information
mprins committed Oct 7, 2024
1 parent 34ca1d9 commit 8170ae6
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -73,6 +74,8 @@
import org.tailormap.api.repository.SearchIndexRepository;
import org.tailormap.api.repository.UploadRepository;
import org.tailormap.api.repository.UserRepository;
import org.tailormap.api.scheduling.DummyJob;
import org.tailormap.api.scheduling.JobCreator;
import org.tailormap.api.security.InternalAdminAuthentication;
import org.tailormap.api.solr.SolrHelper;
import org.tailormap.api.solr.SolrService;
Expand Down Expand Up @@ -113,6 +116,7 @@ public class PopulateTestData {
private final GeoServiceRepository geoServiceRepository;
private final GeoServiceHelper geoServiceHelper;
private final SolrService solrService;
private final JobCreator jobCreator;

private final FeatureSourceRepository featureSourceRepository;
private final ApplicationRepository applicationRepository;
Expand All @@ -129,6 +133,7 @@ public PopulateTestData(
GeoServiceRepository geoServiceRepository,
GeoServiceHelper geoServiceHelper,
SolrService solrService,
JobCreator jobCreator,
FeatureSourceRepository featureSourceRepository,
ApplicationRepository applicationRepository,
ConfigurationRepository configurationRepository,
Expand All @@ -142,6 +147,7 @@ public PopulateTestData(
this.geoServiceRepository = geoServiceRepository;
this.geoServiceHelper = geoServiceHelper;
this.solrService = solrService;
this.jobCreator = jobCreator;
this.featureSourceRepository = featureSourceRepository;
this.applicationRepository = applicationRepository;
this.configurationRepository = configurationRepository;
Expand All @@ -164,6 +170,7 @@ public void populate() throws Exception {
} catch (Exception e) {
logger.error("Exception creating Solr Index for testdata (continuing)", e);
}
createDummyJobs();
} finally {
InternalAdminAuthentication.clearSecurityContextAuthentication();
}
Expand Down Expand Up @@ -1454,4 +1461,25 @@ public void createSolrIndex() throws Exception {
}
}
}

private void createDummyJobs() {
logger.info("Creating dummy jobs");
try {
logger.info(
"Created minutely job with key: {}",
jobCreator.createJob(
DummyJob.class,
Map.of("type", "dummy", "foo", "bar", "when", "every minute"), /* run every minute */
"0 0/1 * 1/1 * ? *"));
logger.info(
"Created hourly job with key: {}",
jobCreator.createJob(
DummyJob.class,
Map.of("type", "dummy", "foo", "bar", "when", "every hour"), /* run every hour */
"0 0 0/1 1/1 * ? *"));

} catch (SchedulerException e) {
logger.error("Error creating dummy job", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,20 @@
import static java.net.HttpURLConnection.HTTP_ACCEPTED;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatusCode;
Expand All @@ -36,6 +44,12 @@ public class TaskAdminController {
private static final Logger logger =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

private final Scheduler scheduler;

public TaskAdminController(Scheduler scheduler) {
this.scheduler = scheduler;
}

@Operation(
summary = "List all tasks, optionally filtered by type",
description = "This will return a list of all tasks, optionally filtered by job type")
Expand All @@ -44,34 +58,46 @@ public class TaskAdminController {
produces = MediaType.APPLICATION_JSON_VALUE)
@ApiResponse(
responseCode = "200",
description = "List of all tasks",
description = "List of all tasks, this list may be empty",
content =
@Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
schema =
@Schema(
example =
"{\"tasks\":[{\"uuid\":\"6308d26e-fe1e-4268-bb28-20db2cd06914\",\"type\":\"TestJob\"},{\"uuid\":\"d5ce9152-e90e-4b5a-b129-3b2366cabca8\",\"type\":\"label\"}]}")))
public ResponseEntity<?> list(@RequestParam(required = false) String type) {
public ResponseEntity<?> list(@RequestParam(required = false) String type)
throws SchedulerException {
logger.debug("Listing all tasks (optional type filter: {})", (null == type ? "all" : type));
List<ObjectNode> tasks = new ArrayList<>();

GroupMatcher<JobKey> groupMatcher =
(null == type ? GroupMatcher.anyGroup() : GroupMatcher.groupEquals(type));
scheduler.getJobKeys(groupMatcher).stream()
.map(
jobKey -> {
try {
return scheduler.getJobDetail(jobKey);
} catch (SchedulerException e) {
logger.error("Error getting job detail", e);
return null;
}
})
.filter(Objects::nonNull)
.forEach(
jobDetail -> {
logger.debug("Job: {}", jobDetail.getKey());
tasks.add(
new ObjectMapper()
.createObjectNode()
.put("uuid", jobDetail.getKey().getName())
.put("type", jobDetail.getJobDataMap().getString("type")));
});

return ResponseEntity.ok(
new ObjectMapper()
.createObjectNode()
.set(
"tasks",
new ObjectMapper()
.createArrayNode()
.add(
new ObjectMapper()
.createObjectNode()
.put("uuid", "6308d26e-fe1e-4268-bb28-20db2cd06914")
.put("type", "dummy"))
.add(
new ObjectMapper()
.createObjectNode()
.put("uuid", "d5ce9152-e90e-4b5a-b129-3b2366cabca8")
.put("type", "dummy"))));
.set("tasks", new ObjectMapper().createArrayNode().addAll(tasks)));
}

@Operation(
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/org/tailormap/api/scheduling/DummyJob.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2024 B3Partners B.V.
*
* SPDX-License-Identifier: MIT
*/
package org.tailormap.api.scheduling;

import java.lang.invoke.MethodHandles;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.NonNull;
import org.springframework.scheduling.quartz.QuartzJobBean;

/** Dummy job for testing purposes. This will only log messages. */
@DisallowConcurrentExecution
public class DummyJob extends QuartzJobBean {
private static final Logger logger =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

@Override
protected void executeInternal(@NonNull JobExecutionContext context) {
logger.info("Dummy job executing, details follow:");
context
.getJobDetail()
.getJobDataMap()
.forEach((key, value) -> logger.info(" Key: {}, Value: {}", key, value));
}
}
81 changes: 81 additions & 0 deletions src/main/java/org/tailormap/api/scheduling/JobCreator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright (C) 2024 B3Partners B.V.
*
* SPDX-License-Identifier: MIT
*/
package org.tailormap.api.scheduling;

import java.lang.invoke.MethodHandles;
import java.util.Map;
import java.util.UUID;
import org.quartz.CronScheduleBuilder;
import org.quartz.DateBuilder;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.ObjectAlreadyExistsException;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class JobCreator {
private static final Logger logger =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

private final Scheduler scheduler;

public JobCreator(Scheduler scheduler) {
this.scheduler = scheduler;
}

/**
* Create a job and schedule it with a cron expression.
*
* @param job the job to create
* @param jobData a map with job data, the "type" key is mandatory
* @param cronExpression the cron expression
* @return the job name
* @throws SchedulerException if the job could not be scheduled
*/
public String createJob(Class<? extends Job> job, Map<?, ?> jobData, String cronExpression)
throws SchedulerException {

// Create a job
JobDetail jobDetail =
JobBuilder.newJob()
.withIdentity(new JobKey(UUID.randomUUID().toString(), jobData.get("type").toString()))
.withDescription(/* TODO add parameter of get from jobData */ job.getSimpleName())
.usingJobData(new JobDataMap(jobData))
.ofType(job)
.build();

// Create a trigger
Trigger trigger =
TriggerBuilder.newTrigger()
.withIdentity(jobDetail.getKey().getName(), jobDetail.getKey().getGroup())
.startAt(DateBuilder.futureDate(30, DateBuilder.IntervalUnit.SECOND))
.withSchedule(
CronScheduleBuilder.cronSchedule(cronExpression)
.withMisfireHandlingInstructionFireAndProceed())
.build();

try {
scheduler.scheduleJob(jobDetail, trigger);
} catch (ObjectAlreadyExistsException ex) {
logger.warn(
"Job {} with trigger {} has not bean added to scheduler as it already exists.",
jobDetail.getKey(),
trigger.getKey());
return null;
}

return jobDetail.getKey().getName();
}
}
Loading

0 comments on commit 8170ae6

Please sign in to comment.