#56 added the simpleTrigger to the API

This commit is contained in:
Fabio Formosa
2022-03-05 20:48:53 +01:00
parent 851100b774
commit 54999ce735
31 changed files with 741 additions and 132 deletions

View File

@@ -1,5 +1,6 @@
package it.fabioformosa.quartzmanager.aspects; package it.fabioformosa.quartzmanager.aspects;
import org.quartz.JobExecutionContext;
import org.quartz.SchedulerException; import org.quartz.SchedulerException;
/** /**
@@ -11,6 +12,6 @@ import org.quartz.SchedulerException;
*/ */
public interface ProgressNotifier { public interface ProgressNotifier {
void send() throws SchedulerException; void send(JobExecutionContext jobExecutionContext) throws SchedulerException;
} }

View File

@@ -1,17 +1,11 @@
package it.fabioformosa.quartzmanager.aspects; package it.fabioformosa.quartzmanager.aspects;
import it.fabioformosa.quartzmanager.dto.TriggerStatus; import it.fabioformosa.quartzmanager.dto.TriggerStatus;
import it.fabioformosa.quartzmanager.services.SchedulerService; import org.quartz.*;
import org.quartz.DailyTimeIntervalTrigger;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessageSendingOperations; import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/** /**
* *
* Notify the progress of the trigger through websocket * Notify the progress of the trigger through websocket
@@ -29,8 +23,8 @@ public class WebSocketProgressNotifier implements ProgressNotifier {
// @Resource // @Resource
// private Scheduler scheduler; // private Scheduler scheduler;
@Resource // @Resource
private SchedulerService schedulerService; // private LegacySchedulerService schedulerService;
// @Resource // @Resource
// private TriggerMonitor triggerMonitor; // private TriggerMonitor triggerMonitor;
@@ -42,34 +36,26 @@ public class WebSocketProgressNotifier implements ProgressNotifier {
// } // }
@Override @Override
public void send() throws SchedulerException { public void send(JobExecutionContext jobExecutionContext) throws SchedulerException {
TriggerStatus currTriggerStatus = new TriggerStatus(); TriggerStatus currTriggerStatus = new TriggerStatus();
Trigger trigger = schedulerService.getOneSimpleTrigger().get(); Trigger trigger = jobExecutionContext.getTrigger();
currTriggerStatus.setFinalFireTime(trigger.getFinalFireTime()); currTriggerStatus.setFinalFireTime(trigger.getFinalFireTime());
currTriggerStatus.setNextFireTime(trigger.getNextFireTime()); currTriggerStatus.setNextFireTime(trigger.getNextFireTime());
currTriggerStatus.setPreviousFireTime(trigger.getPreviousFireTime()); currTriggerStatus.setPreviousFireTime(trigger.getPreviousFireTime());
int timesTriggered = 0;
int repeatCount = 0;
if (trigger instanceof SimpleTrigger) { if (trigger instanceof SimpleTrigger) {
SimpleTrigger simpleTrigger = (SimpleTrigger) trigger; SimpleTrigger simpleTrigger = (SimpleTrigger) trigger;
timesTriggered = simpleTrigger.getTimesTriggered(); currTriggerStatus.setRepeatCount(simpleTrigger.getRepeatCount() + 1);
repeatCount = simpleTrigger.getRepeatCount(); currTriggerStatus.setTimesTriggered(simpleTrigger.getTimesTriggered());
} else if (trigger instanceof DailyTimeIntervalTrigger) { } else if (trigger instanceof DailyTimeIntervalTrigger) {
DailyTimeIntervalTrigger dailyTrigger = (DailyTimeIntervalTrigger) trigger; DailyTimeIntervalTrigger dailyTrigger = (DailyTimeIntervalTrigger) trigger;
timesTriggered = dailyTrigger.getTimesTriggered(); currTriggerStatus.setRepeatCount(dailyTrigger.getRepeatCount() + 1);
repeatCount = dailyTrigger.getRepeatCount();
} }
Trigger jobTrigger = schedulerService.getOneSimpleTrigger().get(); JobDetail jobDetail = jobExecutionContext.getJobDetail();
if (jobTrigger != null && jobTrigger.getJobKey() != null) { currTriggerStatus.setJobKey(jobDetail.getKey().getName());
currTriggerStatus.setJobKey(jobTrigger.getJobKey().getName()); currTriggerStatus.setJobClass(trigger.getClass().getSimpleName());
currTriggerStatus.setJobClass(jobTrigger.getClass().getSimpleName());
currTriggerStatus.setTimesTriggered(timesTriggered);
currTriggerStatus.setRepeatCount(repeatCount + 1);
}
messagingTemplate.convertAndSend("/topic/progress", currTriggerStatus); messagingTemplate.convertAndSend("/topic/progress", currTriggerStatus);
} }

View File

@@ -0,0 +1,10 @@
package it.fabioformosa.quartzmanager.controllers;
import org.springframework.beans.factory.annotation.Value;
public class AbstractTriggerController {
@Value("${quartz-manager.jobClass}")
protected String jobClassname;
}

View File

@@ -10,7 +10,7 @@ import it.fabioformosa.quartzmanager.dto.SchedulerConfigParam;
import it.fabioformosa.quartzmanager.dto.SchedulerDTO; import it.fabioformosa.quartzmanager.dto.SchedulerDTO;
import it.fabioformosa.quartzmanager.dto.TriggerStatus; import it.fabioformosa.quartzmanager.dto.TriggerStatus;
import it.fabioformosa.quartzmanager.enums.SchedulerStates; import it.fabioformosa.quartzmanager.enums.SchedulerStates;
import it.fabioformosa.quartzmanager.services.SchedulerService; import it.fabioformosa.quartzmanager.services.LegacySchedulerService;
import org.quartz.SchedulerException; import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger; import org.quartz.SimpleTrigger;
import org.quartz.impl.triggers.SimpleTriggerImpl; import org.quartz.impl.triggers.SimpleTriggerImpl;
@@ -40,10 +40,10 @@ public class SchedulerController {
private final Logger log = LoggerFactory.getLogger(SchedulerController.class); private final Logger log = LoggerFactory.getLogger(SchedulerController.class);
private SchedulerService schedulerService; private LegacySchedulerService legacySchedulerService;
public SchedulerController(SchedulerService schedulerService, ConversionService conversionService) { public SchedulerController(LegacySchedulerService legacySchedulerService, ConversionService conversionService) {
this.schedulerService = schedulerService; this.legacySchedulerService = legacySchedulerService;
this.conversionService = conversionService; this.conversionService = conversionService;
} }
@@ -60,7 +60,7 @@ public class SchedulerController {
}) })
public SchedulerConfigParam getConfig() throws SchedulerException { public SchedulerConfigParam getConfig() throws SchedulerException {
log.debug("SCHEDULER - GET CONFIG params"); log.debug("SCHEDULER - GET CONFIG params");
SchedulerConfigParam schedulerConfigParam = schedulerService.getOneSimpleTrigger() SchedulerConfigParam schedulerConfigParam = legacySchedulerService.getOneSimpleTrigger()
.map(SchedulerController::fromSimpleTriggerToSchedulerConfigParam) .map(SchedulerController::fromSimpleTriggerToSchedulerConfigParam)
.orElse(new SchedulerConfigParam(0L, 0, 0)); .orElse(new SchedulerConfigParam(0L, 0, 0));
return schedulerConfigParam; return schedulerConfigParam;
@@ -69,7 +69,7 @@ public class SchedulerController {
public static SchedulerConfigParam fromSimpleTriggerToSchedulerConfigParam(SimpleTrigger simpleTrigger){ public static SchedulerConfigParam fromSimpleTriggerToSchedulerConfigParam(SimpleTrigger simpleTrigger){
int timesTriggered = simpleTrigger.getTimesTriggered(); int timesTriggered = simpleTrigger.getTimesTriggered();
int maxCount = simpleTrigger.getRepeatCount() + 1; int maxCount = simpleTrigger.getRepeatCount() + 1;
long triggersPerDay = SchedulerService.fromMillsIntervalToTriggerPerDay(simpleTrigger.getRepeatInterval()); long triggersPerDay = LegacySchedulerService.fromMillsIntervalToTriggerPerDay(simpleTrigger.getRepeatInterval());
return new SchedulerConfigParam(triggersPerDay, maxCount, timesTriggered); return new SchedulerConfigParam(triggersPerDay, maxCount, timesTriggered);
} }
@@ -82,7 +82,7 @@ public class SchedulerController {
}) })
public SchedulerDTO getScheduler() { public SchedulerDTO getScheduler() {
log.debug("SCHEDULER - GET Scheduler..."); log.debug("SCHEDULER - GET Scheduler...");
SchedulerDTO schedulerDTO = conversionService.convert(schedulerService.getScheduler(), SchedulerDTO.class); SchedulerDTO schedulerDTO = conversionService.convert(legacySchedulerService.getScheduler(), SchedulerDTO.class);
return schedulerDTO; return schedulerDTO;
} }
@@ -98,7 +98,7 @@ public class SchedulerController {
log.trace("SCHEDULER - GET PROGRESS INFO"); log.trace("SCHEDULER - GET PROGRESS INFO");
TriggerStatus progress = new TriggerStatus(); TriggerStatus progress = new TriggerStatus();
SimpleTriggerImpl jobTrigger = (SimpleTriggerImpl) schedulerService.getOneSimpleTrigger().get(); SimpleTriggerImpl jobTrigger = (SimpleTriggerImpl) legacySchedulerService.getOneSimpleTrigger().get();
if (jobTrigger != null && jobTrigger.getJobKey() != null) { if (jobTrigger != null && jobTrigger.getJobKey() != null) {
progress.setJobKey(jobTrigger.getJobKey().getName()); progress.setJobKey(jobTrigger.getJobKey().getName());
progress.setJobClass(jobTrigger.getClass().getSimpleName()); progress.setJobClass(jobTrigger.getClass().getSimpleName());
@@ -122,9 +122,9 @@ public class SchedulerController {
public Map<String, String> getStatus() throws SchedulerException { public Map<String, String> getStatus() throws SchedulerException {
log.trace("SCHEDULER - GET STATUS"); log.trace("SCHEDULER - GET STATUS");
String schedulerState = ""; String schedulerState = "";
if (schedulerService.getScheduler().isShutdown() || !schedulerService.getScheduler().isStarted()) if (legacySchedulerService.getScheduler().isShutdown() || !legacySchedulerService.getScheduler().isStarted())
schedulerState = SchedulerStates.STOPPED.toString(); schedulerState = SchedulerStates.STOPPED.toString();
else if (schedulerService.getScheduler().isStarted() && schedulerService.getScheduler().isInStandbyMode()) else if (legacySchedulerService.getScheduler().isStarted() && legacySchedulerService.getScheduler().isInStandbyMode())
schedulerState = SchedulerStates.PAUSED.toString(); schedulerState = SchedulerStates.PAUSED.toString();
else else
schedulerState = SchedulerStates.RUNNING.toString(); schedulerState = SchedulerStates.RUNNING.toString();
@@ -139,7 +139,7 @@ public class SchedulerController {
@ResponseStatus(HttpStatus.NO_CONTENT) @ResponseStatus(HttpStatus.NO_CONTENT)
public void pause() throws SchedulerException { public void pause() throws SchedulerException {
log.info("SCHEDULER - PAUSE COMMAND"); log.info("SCHEDULER - PAUSE COMMAND");
schedulerService.getScheduler().standby(); legacySchedulerService.getScheduler().standby();
} }
@GetMapping("/resume") @GetMapping("/resume")
@@ -150,7 +150,7 @@ public class SchedulerController {
@ResponseStatus(HttpStatus.NO_CONTENT) @ResponseStatus(HttpStatus.NO_CONTENT)
public void resume() throws SchedulerException { public void resume() throws SchedulerException {
log.info("SCHEDULER - RESUME COMMAND"); log.info("SCHEDULER - RESUME COMMAND");
schedulerService.getScheduler().start(); legacySchedulerService.getScheduler().start();
} }
@GetMapping("/run") @GetMapping("/run")
@@ -161,7 +161,7 @@ public class SchedulerController {
@ResponseStatus(HttpStatus.NO_CONTENT) @ResponseStatus(HttpStatus.NO_CONTENT)
public void run() throws SchedulerException { public void run() throws SchedulerException {
log.info("SCHEDULER - START COMMAND"); log.info("SCHEDULER - START COMMAND");
schedulerService.getScheduler().start(); legacySchedulerService.getScheduler().start();
} }
@GetMapping("/stop") @GetMapping("/stop")
@@ -172,7 +172,7 @@ public class SchedulerController {
@ResponseStatus(HttpStatus.NO_CONTENT) @ResponseStatus(HttpStatus.NO_CONTENT)
public void stop() throws SchedulerException { public void stop() throws SchedulerException {
log.info("SCHEDULER - STOP COMMAND"); log.info("SCHEDULER - STOP COMMAND");
schedulerService.getScheduler().shutdown(true); legacySchedulerService.getScheduler().shutdown(true);
} }
} }

View File

@@ -0,0 +1,91 @@
package it.fabioformosa.quartzmanager.controllers;
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 io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import it.fabioformosa.quartzmanager.dto.SimpleTriggerCommandDTO;
import it.fabioformosa.quartzmanager.dto.SimpleTriggerInputDTO;
import it.fabioformosa.quartzmanager.dto.SimpleTriggerDTO;
import it.fabioformosa.quartzmanager.dto.TriggerDTO;
import it.fabioformosa.quartzmanager.exceptions.TriggerNotFoundException;
import it.fabioformosa.quartzmanager.services.SimpleTriggerSchedulerService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.SchedulerException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@Slf4j
@RequestMapping(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL)
@SecurityRequirement(name = "basic-auth")
@RestController
public class SimpleTriggerController extends AbstractTriggerController {
static public final String SIMPLE_TRIGGER_CONTROLLER_BASE_URL = "/quartz-manager/simple-triggers";
private SimpleTriggerSchedulerService simpleSchedulerService;
public SimpleTriggerController(SimpleTriggerSchedulerService simpleSchedulerService) {
this.simpleSchedulerService = simpleSchedulerService;
}
@GetMapping("/{name}")
@Operation(summary = "Get a simple trigger by name")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Got the trigger by its name",
content = { @Content(mediaType = "application/json",
schema = @Schema(implementation = SimpleTriggerDTO.class)) }),
@ApiResponse(responseCode = "404", description = "Trigger not found",
content = @Content)
})
public SimpleTriggerDTO getSimpleTrigger(@PathVariable String name) throws SchedulerException, TriggerNotFoundException {
return simpleSchedulerService.getSimpleTriggerByName(name);
}
@PostMapping("/{name}")
@ResponseStatus(HttpStatus.CREATED)
@Operation(summary = "Create a new simple trigger")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "Created a new simple trigger",
content = { @Content(mediaType = "application/json",
schema = @Schema(implementation = TriggerDTO.class)) }),
@ApiResponse(responseCode = "400", description = "Invalid config supplied",
content = @Content)
})
public SimpleTriggerDTO postSimpleTrigger(@PathVariable String name, @Valid @RequestBody SimpleTriggerInputDTO simpleTriggerInputDTO) throws SchedulerException, ClassNotFoundException {
log.info("SIMPLE TRIGGER - CREATING a SimpleTrigger {} {}", name, simpleTriggerInputDTO);
SimpleTriggerCommandDTO simpleTriggerCommandDTO = SimpleTriggerCommandDTO.builder()
.triggerName(name)
.simpleTriggerInputDTO(simpleTriggerInputDTO)
.build();
SimpleTriggerDTO newTriggerDTO = simpleSchedulerService.scheduleSimpleTrigger(jobClassname, simpleTriggerCommandDTO);
log.info("SIMPLE TRIGGER - CREATED a SimpleTrigger {}", newTriggerDTO);
return newTriggerDTO;
}
@PutMapping("/{name}")
@Operation(summary = "Reschedule a simple trigger")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Rescheduled a simple trigger",
content = { @Content(mediaType = "application/json",
schema = @Schema(implementation = TriggerDTO.class)) }),
@ApiResponse(responseCode = "400", description = "Invalid config supplied",
content = @Content)
})
public TriggerDTO rescheduleSimpleTrigger(@PathVariable String name, @Valid @RequestBody SimpleTriggerInputDTO simpleTriggerInputDTO) throws SchedulerException {
log.info("SIMPLE TRIGGER - RESCHEDULING the trigger {} {}", name, simpleTriggerInputDTO);
SimpleTriggerCommandDTO simpleTriggerCommandDTO = SimpleTriggerCommandDTO.builder()
.triggerName(name)
.simpleTriggerInputDTO(simpleTriggerInputDTO)
.build();
TriggerDTO triggerDTO = simpleSchedulerService.rescheduleSimpleTrigger(simpleTriggerCommandDTO);
log.info("SIMPLE TRIGGER - RESCHEDULED the trigger {}", triggerDTO);
return triggerDTO;
}
}

View File

@@ -8,10 +8,10 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import it.fabioformosa.quartzmanager.dto.SchedulerConfigParam; import it.fabioformosa.quartzmanager.dto.SchedulerConfigParam;
import it.fabioformosa.quartzmanager.dto.TriggerDTO; import it.fabioformosa.quartzmanager.dto.TriggerDTO;
import it.fabioformosa.quartzmanager.services.SchedulerService; import it.fabioformosa.quartzmanager.exceptions.TriggerNotFoundException;
import it.fabioformosa.quartzmanager.services.LegacySchedulerService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.quartz.SchedulerException; import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -21,24 +21,23 @@ import javax.validation.Valid;
@RequestMapping(TriggerController.TRIGGER_CONTROLLER_BASE_URL) @RequestMapping(TriggerController.TRIGGER_CONTROLLER_BASE_URL)
@SecurityRequirement(name = "basic-auth") @SecurityRequirement(name = "basic-auth")
@RestController @RestController
public class TriggerController { public class TriggerController extends AbstractTriggerController {
static public final String TRIGGER_CONTROLLER_BASE_URL = "/quartz-manager/triggers"; static public final String TRIGGER_CONTROLLER_BASE_URL = "/quartz-manager/triggers";
static public final String SIMPLE_TRIGGER_BASE_URL = "/simple-triggers";
@Value("${quartz-manager.jobClass}") private LegacySchedulerService schedulerService;
private String jobClassname;
private SchedulerService schedulerService; public TriggerController(LegacySchedulerService schedulerService) {
public TriggerController(SchedulerService schedulerService) {
this.schedulerService = schedulerService; this.schedulerService = schedulerService;
} }
@GetMapping("/{name}") @GetMapping("/{name}")
public TriggerDTO getTrigger(@PathVariable String name) throws SchedulerException { public TriggerDTO getTrigger(@PathVariable String name) throws SchedulerException, TriggerNotFoundException {
return schedulerService.getTriggerByName(name); return schedulerService.getLegacyTriggerByName(name);
} }
@Deprecated
@PostMapping("/{name}") @PostMapping("/{name}")
@ResponseStatus(HttpStatus.CREATED) @ResponseStatus(HttpStatus.CREATED)
@Operation(summary = "Create a new trigger") @Operation(summary = "Create a new trigger")
@@ -56,6 +55,8 @@ public class TriggerController {
return newTriggerDTO; return newTriggerDTO;
} }
@PutMapping("/{name}") @PutMapping("/{name}")
@Operation(summary = "Reschedule the trigger") @Operation(summary = "Reschedule the trigger")
@ApiResponses(value = { @ApiResponses(value = {

View File

@@ -0,0 +1,31 @@
package it.fabioformosa.quartzmanager.controllers.advices;
import it.fabioformosa.quartzmanager.exceptions.ExceptionResponse;
import it.fabioformosa.quartzmanager.exceptions.ResourceConflictException;
import it.fabioformosa.quartzmanager.exceptions.TriggerNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
@ControllerAdvice
public class ExceptionHandlingController {
@ExceptionHandler(ResourceConflictException.class)
public ResponseEntity<ExceptionResponse> resourceConflict(ResourceConflictException ex) {
ExceptionResponse response = new ExceptionResponse();
response.setErrorCode("Conflict");
response.setErrorMessage(ex.getMessage());
return new ResponseEntity<ExceptionResponse>(response, HttpStatus.CONFLICT);
}
@ExceptionHandler(TriggerNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
@ResponseBody
public ExceptionResponse triggerNotFound(TriggerNotFoundException ex){
return ExceptionResponse.builder().errorCode(HttpStatus.NOT_FOUND.toString()).errorMessage(ex.getMessage()).build();
}
}

View File

@@ -0,0 +1,32 @@
package it.fabioformosa.quartzmanager.converters;
import it.fabioformosa.quartzmanager.dto.SimpleTriggerCommandDTO;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
@Component
public class SimpleTriggerCommandDTOToSimpleTrigger implements Converter<SimpleTriggerCommandDTO, SimpleTrigger> {
@Override
public SimpleTrigger convert(SimpleTriggerCommandDTO triggerCommandDTO) {
TriggerBuilder<Trigger> triggerTriggerBuilder = TriggerBuilder.newTrigger();
if(triggerCommandDTO.getSimpleTriggerInputDTO().getStartDate() != null)
triggerTriggerBuilder.startAt(triggerCommandDTO.getSimpleTriggerInputDTO().getStartDate());
if(triggerCommandDTO.getSimpleTriggerInputDTO().getEndDate() != null)
triggerTriggerBuilder.endAt(triggerCommandDTO.getSimpleTriggerInputDTO().getEndDate());
SimpleTrigger newSimpleTrigger = triggerTriggerBuilder.withSchedule(
SimpleScheduleBuilder.simpleSchedule()
.withIntervalInMilliseconds(triggerCommandDTO.getSimpleTriggerInputDTO().getRepeatInterval())
.withRepeatCount(triggerCommandDTO.getSimpleTriggerInputDTO().getRepeatCount())
.withMisfireHandlingInstructionNextWithRemainingCount()
)
.withIdentity(triggerCommandDTO.getTriggerName())
.build();
return newSimpleTrigger;
}
}

View File

@@ -0,0 +1,22 @@
package it.fabioformosa.quartzmanager.converters;
import it.fabioformosa.quartzmanager.dto.SimpleTriggerDTO;
import org.quartz.SimpleTrigger;
import org.springframework.stereotype.Component;
@Component
public class SimpleTriggerToSimpleTriggerDTO extends TriggerToTriggerDTO<SimpleTrigger, SimpleTriggerDTO> {
@Override
protected void convert(SimpleTrigger source, SimpleTriggerDTO target) {
super.convert(source, target);
target.setTimesTriggered(source.getTimesTriggered());
target.setRepeatCount(source.getRepeatCount());
target.setRepeatInterval(source.getRepeatInterval());
}
@Override
protected SimpleTriggerDTO createOrRetrieveTarget(SimpleTrigger source) {
return new SimpleTriggerDTO();
}
}

View File

@@ -1,6 +1,6 @@
package it.fabioformosa.quartzmanager.converters; package it.fabioformosa.quartzmanager.converters;
import it.fabioformosa.metamorphosis.core.converters.AbstractBaseConverterToDTO; import it.fabioformosa.metamorphosis.core.converters.AbstractBaseConverter;
import it.fabioformosa.quartzmanager.dto.JobKeyDTO; import it.fabioformosa.quartzmanager.dto.JobKeyDTO;
import it.fabioformosa.quartzmanager.dto.TriggerDTO; import it.fabioformosa.quartzmanager.dto.TriggerDTO;
import it.fabioformosa.quartzmanager.dto.TriggerKeyDTO; import it.fabioformosa.quartzmanager.dto.TriggerKeyDTO;
@@ -10,10 +10,10 @@ import org.quartz.TriggerKey;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
public class TriggerToTriggerDTO extends AbstractBaseConverterToDTO<Trigger, TriggerDTO> { public class TriggerToTriggerDTO<S extends Trigger, T extends TriggerDTO> extends AbstractBaseConverter<S, T> {
@Override @Override
protected void convert(Trigger source, TriggerDTO target) { protected void convert(S source, T target) {
TriggerKey triggerKey = source.getKey(); TriggerKey triggerKey = source.getKey();
TriggerKeyDTO triggerKeyDTO = conversionService.convert(triggerKey, TriggerKeyDTO.class); TriggerKeyDTO triggerKeyDTO = conversionService.convert(triggerKey, TriggerKeyDTO.class);
target.setTriggerKeyDTO(triggerKeyDTO); target.setTriggerKeyDTO(triggerKeyDTO);
@@ -30,7 +30,11 @@ public class TriggerToTriggerDTO extends AbstractBaseConverterToDTO<Trigger, Tri
JobKey jobKey = source.getJobKey(); JobKey jobKey = source.getJobKey();
JobKeyDTO jobKeyDTO = conversionService.convert(jobKey, JobKeyDTO.class); JobKeyDTO jobKeyDTO = conversionService.convert(jobKey, JobKeyDTO.class);
target.setJobKeyDTO(jobKeyDTO); target.setJobKeyDTO(jobKeyDTO);
}
@Override
protected T createOrRetrieveTarget(S source) {
return (T) new TriggerDTO();
} }
} }

View File

@@ -0,0 +1,13 @@
package it.fabioformosa.quartzmanager.dto;
import lombok.*;
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
@ToString
public class SimpleTriggerCommandDTO {
private String triggerName;
private SimpleTriggerInputDTO simpleTriggerInputDTO;
}

View File

@@ -0,0 +1,16 @@
package it.fabioformosa.quartzmanager.dto;
import lombok.*;
import lombok.experimental.SuperBuilder;
@NoArgsConstructor @AllArgsConstructor
@Data
@ToString(callSuper = true) @EqualsAndHashCode(callSuper = true)
@SuperBuilder
public class SimpleTriggerDTO extends TriggerDTO{
private int repeatCount;
private long repeatInterval;
private int timesTriggered;
}

View File

@@ -0,0 +1,21 @@
package it.fabioformosa.quartzmanager.dto;
import lombok.*;
import lombok.experimental.SuperBuilder;
import javax.validation.constraints.NotNull;
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Data
@ToString(callSuper = true)
public class SimpleTriggerInputDTO extends TriggerCommandDTO {
@NotNull
private Integer repeatCount;
@NotNull
Long repeatInterval;
}

View File

@@ -0,0 +1,22 @@
package it.fabioformosa.quartzmanager.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.*;
import lombok.experimental.SuperBuilder;
import java.util.Date;
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@ToString
@Data
public class TriggerCommandDTO {
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
private Date startDate;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
private Date endDate;
}

View File

@@ -1,16 +1,16 @@
package it.fabioformosa.quartzmanager.dto; package it.fabioformosa.quartzmanager.dto;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.util.Date; import java.util.Date;
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@Data @Data
@Builder @SuperBuilder
public class TriggerDTO { public class TriggerDTO {
private TriggerKeyDTO triggerKeyDTO; private TriggerKeyDTO triggerKeyDTO;
private int priority; private int priority;

View File

@@ -1,18 +0,0 @@
package it.fabioformosa.quartzmanager.exceptions;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class ExceptionHandlingController {
@ExceptionHandler(ResourceConflictException.class)
public ResponseEntity<ExceptionResponse> resourceConflict(ResourceConflictException ex) {
ExceptionResponse response = new ExceptionResponse();
response.setErrorCode("Conflict");
response.setErrorMessage(ex.getMessage());
return new ResponseEntity<ExceptionResponse>(response, HttpStatus.CONFLICT);
}
}

View File

@@ -1,25 +1,15 @@
package it.fabioformosa.quartzmanager.exceptions; package it.fabioformosa.quartzmanager.exceptions;
public class ExceptionResponse { import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
public class ExceptionResponse {
private String errorCode; private String errorCode;
private String errorMessage; private String errorMessage;
public ExceptionResponse() {}
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
} }

View File

@@ -1,5 +1,9 @@
package it.fabioformosa.quartzmanager.exceptions; package it.fabioformosa.quartzmanager.exceptions;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class ResourceConflictException extends RuntimeException { public class ResourceConflictException extends RuntimeException {
private static final long serialVersionUID = 1791564636123821405L; private static final long serialVersionUID = 1791564636123821405L;
@@ -8,14 +12,7 @@ public class ResourceConflictException extends RuntimeException {
public ResourceConflictException(Long resourceId, String message) { public ResourceConflictException(Long resourceId, String message) {
super(message); super(message);
setResourceId(resourceId);
}
public Long getResourceId() {
return resourceId;
}
public void setResourceId(Long resourceId) {
this.resourceId = resourceId; this.resourceId = resourceId;
} }
} }

View File

@@ -0,0 +1,16 @@
package it.fabioformosa.quartzmanager.exceptions;
import lombok.Getter;
import lombok.ToString;
@ToString
@Getter
public class TriggerNotFoundException extends Exception {
private String name;
public TriggerNotFoundException(String name) {
super("Trigger with name " + name + " not found!");
this.name = name;
}
}

View File

@@ -1,7 +1,7 @@
package it.fabioformosa.quartzmanager.jobs; package it.fabioformosa.quartzmanager.jobs;
import javax.annotation.Resource; import it.fabioformosa.quartzmanager.aspects.ProgressNotifier;
import it.fabioformosa.quartzmanager.jobs.entities.LogRecord;
import org.quartz.Job; import org.quartz.Job;
import org.quartz.JobExecutionContext; import org.quartz.JobExecutionContext;
import org.quartz.SchedulerException; import org.quartz.SchedulerException;
@@ -10,13 +10,12 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessageSendingOperations; import org.springframework.messaging.simp.SimpMessageSendingOperations;
import it.fabioformosa.quartzmanager.aspects.ProgressNotifier; import javax.annotation.Resource;
import it.fabioformosa.quartzmanager.jobs.entities.LogRecord;
/** /**
* Extends this class to create a job that produces LogRecord to be displayed * Extends this class to create a job that produces LogRecord to be displayed
* into the GUI panel * into the GUI panel
* *
* @author Fabio.Formosa * @author Fabio.Formosa
* *
*/ */
@@ -42,7 +41,7 @@ public abstract class AbstractLoggingJob implements Job {
try { try {
LogRecord logMsg = doIt(jobExecutionContext); LogRecord logMsg = doIt(jobExecutionContext);
logAndSend(logMsg); logAndSend(logMsg);
progressNotifier.send(); progressNotifier.send(jobExecutionContext);
} catch (SchedulerException e) { } catch (SchedulerException e) {
log.error("Error updating progress " + e.getMessage()); log.error("Error updating progress " + e.getMessage());
} }
@@ -54,4 +53,4 @@ public abstract class AbstractLoggingJob implements Job {
messagingTemplate.convertAndSend("/topic/logs", logRecord); messagingTemplate.convertAndSend("/topic/logs", logRecord);
} }
} }

View File

@@ -0,0 +1,26 @@
package it.fabioformosa.quartzmanager.services;
import it.fabioformosa.quartzmanager.exceptions.TriggerNotFoundException;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerKey;
import org.springframework.core.convert.ConversionService;
public class AbstractSchedulerService {
protected Scheduler scheduler;
protected ConversionService conversionService;
public AbstractSchedulerService(Scheduler scheduler, ConversionService conversionService) {
this.scheduler = scheduler;
this.conversionService = conversionService;
}
protected Trigger getTriggerByName(String name) throws SchedulerException, TriggerNotFoundException {
Trigger trigger = scheduler.getTrigger(new TriggerKey(name));
if(trigger == null)
throw new TriggerNotFoundException(name);
return trigger;
}
}

View File

@@ -3,6 +3,7 @@ package it.fabioformosa.quartzmanager.services;
import it.fabioformosa.quartzmanager.common.utils.Try; import it.fabioformosa.quartzmanager.common.utils.Try;
import it.fabioformosa.quartzmanager.dto.SchedulerConfigParam; import it.fabioformosa.quartzmanager.dto.SchedulerConfigParam;
import it.fabioformosa.quartzmanager.dto.TriggerDTO; import it.fabioformosa.quartzmanager.dto.TriggerDTO;
import it.fabioformosa.quartzmanager.exceptions.TriggerNotFoundException;
import org.quartz.*; import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher; import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
@@ -10,26 +11,24 @@ import org.springframework.stereotype.Service;
import java.util.Optional; import java.util.Optional;
import static org.quartz.TriggerBuilder.newTrigger;
@Service @Service
public class SchedulerService { public class LegacySchedulerService extends AbstractSchedulerService {
public static final int MILLS_IN_A_DAY = 1000 * 60 * 60 * 24; public static final int MILLS_IN_A_DAY = 1000 * 60 * 60 * 24;
public static final int SEC_IN_A_DAY = 60 * 60 * 24; public static final int SEC_IN_A_DAY = 60 * 60 * 24;
private Scheduler scheduler; public LegacySchedulerService(Scheduler scheduler, ConversionService conversionService) {
private ConversionService conversionService; super(scheduler, conversionService);
public SchedulerService(Scheduler scheduler, ConversionService conversionService) {
this.scheduler = scheduler;
this.conversionService = conversionService;
} }
public static int fromTriggerPerDayToMillsInterval(long triggerPerDay) { public static int fromTriggerPerDayToMillsInterval(long triggerPerDay) {
return (int) Math.ceil(Long.valueOf(SchedulerService.MILLS_IN_A_DAY) / triggerPerDay); // with ceil the triggerPerDay is a max value return (int) Math.ceil(Long.valueOf(LegacySchedulerService.MILLS_IN_A_DAY) / triggerPerDay); // with ceil the triggerPerDay is a max value
} }
public static int fromTriggerPerDayToSecInterval(long triggerPerDay) { public static int fromTriggerPerDayToSecInterval(long triggerPerDay) {
return (int) Math.ceil(Long.valueOf(SchedulerService.SEC_IN_A_DAY) / triggerPerDay); return (int) Math.ceil(Long.valueOf(LegacySchedulerService.SEC_IN_A_DAY) / triggerPerDay);
} }
public static long fromMillsIntervalToTriggerPerDay(long repeatIntervalInMills) { public static long fromMillsIntervalToTriggerPerDay(long repeatIntervalInMills) {
@@ -59,10 +58,12 @@ public class SchedulerService {
.findFirst(); .findFirst();
} }
public TriggerDTO getTriggerByName(String name) throws SchedulerException { public TriggerDTO getLegacyTriggerByName(String name) throws SchedulerException, TriggerNotFoundException {
Trigger trigger = scheduler.getTrigger(new TriggerKey(name)); Trigger trigger = getTriggerByName(name);
return conversionService.convert(trigger, TriggerDTO.class); return conversionService.convert(trigger, TriggerDTO.class);
} }
public TriggerDTO scheduleNewTrigger(String name, String jobClassname, SchedulerConfigParam config) throws SchedulerException, ClassNotFoundException { public TriggerDTO scheduleNewTrigger(String name, String jobClassname, SchedulerConfigParam config) throws SchedulerException, ClassNotFoundException {
Class<? extends Job> jobClass = (Class<? extends Job>) Class.forName(jobClassname); Class<? extends Job> jobClass = (Class<? extends Job>) Class.forName(jobClassname);
@@ -71,9 +72,9 @@ public class SchedulerService {
.storeDurably(false) .storeDurably(false)
.build(); .build();
int intervalInMills = SchedulerService.fromTriggerPerDayToMillsInterval(config.getTriggerPerDay()); int intervalInMills = LegacySchedulerService.fromTriggerPerDayToMillsInterval(config.getTriggerPerDay());
Trigger newTrigger = TriggerBuilder.newTrigger() Trigger newTrigger = newTrigger()
.withSchedule( .withSchedule(
SimpleScheduleBuilder.simpleSchedule() SimpleScheduleBuilder.simpleSchedule()
.withIntervalInMilliseconds(intervalInMills) .withIntervalInMilliseconds(intervalInMills)
@@ -88,14 +89,16 @@ public class SchedulerService {
return conversionService.convert(newTrigger, TriggerDTO.class); return conversionService.convert(newTrigger, TriggerDTO.class);
} }
public TriggerDTO rescheduleTrigger(String name, SchedulerConfigParam config) throws SchedulerException { public TriggerDTO rescheduleTrigger(String name, SchedulerConfigParam config) throws SchedulerException {
int intervalInMills = SchedulerService.fromTriggerPerDayToMillsInterval(config.getTriggerPerDay()); int intervalInMills = LegacySchedulerService.fromTriggerPerDayToMillsInterval(config.getTriggerPerDay());
Optional<TriggerKey> optionalTriggerKey = getTriggerByKey(name); Optional<TriggerKey> optionalTriggerKey = getTriggerByKey(name);
TriggerKey triggerKey = optionalTriggerKey.orElse(TriggerKey.triggerKey(name)); TriggerKey triggerKey = optionalTriggerKey.orElse(TriggerKey.triggerKey(name));
Trigger trigger = scheduler.getTrigger(triggerKey); Trigger trigger = scheduler.getTrigger(triggerKey);
Trigger newTrigger = TriggerBuilder.newTrigger() Trigger newTrigger = newTrigger()
.withSchedule( .withSchedule(
SimpleScheduleBuilder.simpleSchedule() SimpleScheduleBuilder.simpleSchedule()
.withIntervalInMilliseconds(intervalInMills) .withIntervalInMilliseconds(intervalInMills)

View File

@@ -0,0 +1,48 @@
package it.fabioformosa.quartzmanager.services;
import it.fabioformosa.quartzmanager.dto.SimpleTriggerCommandDTO;
import it.fabioformosa.quartzmanager.dto.SimpleTriggerDTO;
import it.fabioformosa.quartzmanager.dto.TriggerDTO;
import it.fabioformosa.quartzmanager.exceptions.TriggerNotFoundException;
import org.quartz.*;
import org.springframework.core.convert.ConversionService;
import org.springframework.stereotype.Service;
@Service
public class SimpleTriggerSchedulerService extends AbstractSchedulerService {
public SimpleTriggerSchedulerService(Scheduler scheduler, ConversionService conversionService) {
super(scheduler, conversionService);
}
public SimpleTriggerDTO getSimpleTriggerByName(String name) throws SchedulerException, TriggerNotFoundException {
Trigger trigger = getTriggerByName(name);
return conversionService.convert(trigger, SimpleTriggerDTO.class);
}
public SimpleTriggerDTO scheduleSimpleTrigger(String jobClassname, SimpleTriggerCommandDTO triggerCommandDTO) throws SchedulerException, ClassNotFoundException {
Class<? extends Job> jobClass = (Class<? extends Job>) Class.forName(jobClassname);
JobDetail jobDetail = JobBuilder.newJob()
.ofType(jobClass)
.storeDurably(false)
.build();
SimpleTrigger newSimpleTrigger = conversionService.convert(triggerCommandDTO, SimpleTrigger.class);
scheduler.scheduleJob(jobDetail, newSimpleTrigger);
return conversionService.convert(newSimpleTrigger, SimpleTriggerDTO.class);
}
public TriggerDTO rescheduleSimpleTrigger(SimpleTriggerCommandDTO triggerCommandDTO) throws SchedulerException {
//Optional<TriggerKey> optionalTriggerKey = getTriggerByKey(name);
// TriggerKey triggerKey = optionalTriggerKey.orElse(TriggerKey.triggerKey(name));
SimpleTrigger newSimpleTrigger = conversionService.convert(triggerCommandDTO, SimpleTrigger.class);
TriggerKey triggerKey = TriggerKey.triggerKey(triggerCommandDTO.getTriggerName());
scheduler.rescheduleJob(triggerKey, newSimpleTrigger);
return conversionService.convert(newSimpleTrigger, SimpleTriggerDTO.class);
}
}

View File

@@ -0,0 +1,123 @@
package it.fabioformosa.quartzmanager.controllers;
import it.fabioformosa.quartzmanager.QuartManagerApplicationTests;
import it.fabioformosa.quartzmanager.controllers.utils.InvalidSimpleTriggerCommandDTOProvider;
import it.fabioformosa.quartzmanager.controllers.utils.TestUtils;
import it.fabioformosa.quartzmanager.controllers.utils.TriggerUtils;
import it.fabioformosa.quartzmanager.dto.SimpleTriggerCommandDTO;
import it.fabioformosa.quartzmanager.dto.SimpleTriggerDTO;
import it.fabioformosa.quartzmanager.dto.SimpleTriggerInputDTO;
import it.fabioformosa.quartzmanager.exceptions.TriggerNotFoundException;
import it.fabioformosa.quartzmanager.services.SimpleTriggerSchedulerService;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import java.util.Date;
import static org.mockito.ArgumentMatchers.any;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
@ContextConfiguration(classes = {QuartManagerApplicationTests.class})
@WebMvcTest(controllers = SimpleTriggerController.class, properties = {
"quartz-manager.jobClass=it.fabioformosa.quartzmanager.jobs.SampleJob"
})
class SimpleTriggerControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private SimpleTriggerSchedulerService simpleTriggerSchedulerService;
@AfterEach
void cleanUp(){
Mockito.reset(simpleTriggerSchedulerService);
}
@Test
void whenGetIsCalled_thenASimpleTriggerIsReturned() throws Exception {
SimpleTriggerDTO expectedSimpleTriggerDTO = TriggerUtils.getSimpleTriggerInstance("mytrigger");
Mockito.when(simpleTriggerSchedulerService.getSimpleTriggerByName("mytrigger")).thenReturn(expectedSimpleTriggerDTO);
mockMvc.perform(get(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/mytrigger")
.contentType(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().json(TestUtils.toJson(expectedSimpleTriggerDTO)));
}
@Test
void givenAnExistingTrigger_whenGetIsCalled_then404IsReturned() throws Exception {
Mockito.when(simpleTriggerSchedulerService.getSimpleTriggerByName("not_existing_trigger_name")).thenThrow(new TriggerNotFoundException("not_existing_trigger_name"));
mockMvc.perform(get(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/not_existing_trigger_name")
.contentType(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isNotFound());
}
@Test
void givenASimpleTriggerCommandDTO_whenPosted_thenANewSimpleTriggerIsCreated() throws Exception {
SimpleTriggerInputDTO simpleTriggerInputDTO = buildSimpleTriggerCommandDTO();
SimpleTriggerDTO expectedSimpleTriggerDTO = TriggerUtils.getSimpleTriggerInstance("mytrigger", simpleTriggerInputDTO);
Mockito.when(simpleTriggerSchedulerService.scheduleSimpleTrigger(any(), any())).thenReturn(expectedSimpleTriggerDTO);
mockMvc.perform(
post(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/mytrigger")
.contentType(MediaType.APPLICATION_JSON)
.content(TestUtils.toJson(simpleTriggerInputDTO))
)
.andExpect(MockMvcResultMatchers.status().isCreated())
.andExpect(MockMvcResultMatchers.content().json(TestUtils.toJson(expectedSimpleTriggerDTO)))
;
}
private SimpleTriggerInputDTO buildSimpleTriggerCommandDTO() {
return SimpleTriggerInputDTO.builder()
.startDate(new Date())
.repeatCount(20)
.repeatInterval(20000L)
.build();
}
@ParameterizedTest
@ArgumentsSource(InvalidSimpleTriggerCommandDTOProvider.class)
void givenAnInvalidSimpleTriggerCommandDTO_whenPostedANewTrigger_thenAnErrorIsReturned(SimpleTriggerInputDTO invalidSimpleTriggerComandDTO) throws Exception {
mockMvc.perform(post(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/mytrigger")
.contentType(MediaType.APPLICATION_JSON)
.content(TestUtils.toJson(invalidSimpleTriggerComandDTO)))
.andExpect(MockMvcResultMatchers.status().is4xxClientError());
}
@Test
void givenATriggerName_whenPutSimpleTriggerCommandDTO_thenTheSimpleTriggerIsRescheduled() throws Exception {
SimpleTriggerInputDTO simpleTriggerInputDTO = buildSimpleTriggerCommandDTO();
SimpleTriggerDTO expectedSimpleTriggerDTO = TriggerUtils.getSimpleTriggerInstance("mytrigger", simpleTriggerInputDTO);
SimpleTriggerCommandDTO simpleTriggerCommandDTO = SimpleTriggerCommandDTO.builder()
.triggerName("mytrigger")
.simpleTriggerInputDTO(simpleTriggerInputDTO)
.build();
Mockito.when(simpleTriggerSchedulerService.rescheduleSimpleTrigger(simpleTriggerCommandDTO)).thenReturn(expectedSimpleTriggerDTO);
mockMvc.perform(put(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/mytrigger")
.contentType(MediaType.APPLICATION_JSON)
.content(TestUtils.toJson(simpleTriggerInputDTO)))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().json(TestUtils.toJson(expectedSimpleTriggerDTO)));
}
@ParameterizedTest
@ArgumentsSource(InvalidSimpleTriggerCommandDTOProvider.class)
void givenAnInvalidSimpleTriggerCommandDTO_whenATriggerIsRescheduled_thenAnErrorIsReturned(SimpleTriggerInputDTO invalidSimpleTriggerCommandTO) throws Exception {
mockMvc.perform(put(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL + "/mytrigger")
.contentType(MediaType.APPLICATION_JSON)
.content(TestUtils.toJson(invalidSimpleTriggerCommandTO)))
.andExpect(MockMvcResultMatchers.status().is4xxClientError());
}
}

View File

@@ -6,7 +6,7 @@ import it.fabioformosa.quartzmanager.controllers.utils.TestUtils;
import it.fabioformosa.quartzmanager.controllers.utils.TriggerUtils; import it.fabioformosa.quartzmanager.controllers.utils.TriggerUtils;
import it.fabioformosa.quartzmanager.dto.SchedulerConfigParam; import it.fabioformosa.quartzmanager.dto.SchedulerConfigParam;
import it.fabioformosa.quartzmanager.dto.TriggerDTO; import it.fabioformosa.quartzmanager.dto.TriggerDTO;
import it.fabioformosa.quartzmanager.services.SchedulerService; import it.fabioformosa.quartzmanager.services.LegacySchedulerService;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
@@ -25,7 +25,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
@ContextConfiguration(classes = {QuartManagerApplicationTests.class}) @ContextConfiguration(classes = {QuartManagerApplicationTests.class})
@WebMvcTest(controllers = TriggerController.class, properties = { @WebMvcTest(controllers = TriggerController.class, properties = {
"quartz-manager.jobClass=it.fabioformosa.quartzmanager.jobs.myjobs.SampleJob" "quartz-manager.jobClass=it.fabioformosa.quartzmanager.jobs.SampleJob"
}) })
class TriggerControllerTest { class TriggerControllerTest {
@@ -33,7 +33,7 @@ class TriggerControllerTest {
private MockMvc mockMvc; private MockMvc mockMvc;
@MockBean @MockBean
private SchedulerService schedulerService; private LegacySchedulerService schedulerService;
@AfterEach @AfterEach
void cleanUp(){ void cleanUp(){
@@ -67,7 +67,7 @@ class TriggerControllerTest {
@Test @Test
void whenGetIsCalled_thenATriggerIsReturned() throws Exception { void whenGetIsCalled_thenATriggerIsReturned() throws Exception {
TriggerDTO expectedTriggerDTO = TriggerUtils.getTriggerInstance("mytrigger"); TriggerDTO expectedTriggerDTO = TriggerUtils.getTriggerInstance("mytrigger");
Mockito.when(schedulerService.getTriggerByName("mytrigger")).thenReturn(expectedTriggerDTO); Mockito.when(schedulerService.getLegacyTriggerByName("mytrigger")).thenReturn(expectedTriggerDTO);
mockMvc.perform(get(TriggerController.TRIGGER_CONTROLLER_BASE_URL + "/mytrigger") mockMvc.perform(get(TriggerController.TRIGGER_CONTROLLER_BASE_URL + "/mytrigger")
.contentType(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isOk()) .contentType(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isOk())

View File

@@ -0,0 +1,19 @@
package it.fabioformosa.quartzmanager.controllers.utils;
import it.fabioformosa.quartzmanager.dto.SimpleTriggerInputDTO;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import java.util.stream.Stream;
public class InvalidSimpleTriggerCommandDTOProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) throws Exception {
return Stream.of(
Arguments.of(SimpleTriggerInputDTO.builder().build()),
Arguments.of(SimpleTriggerInputDTO.builder().repeatCount(1).build()),
Arguments.of(SimpleTriggerInputDTO.builder().repeatInterval(1L).build())
);
}
}

View File

@@ -1,9 +1,7 @@
package it.fabioformosa.quartzmanager.controllers.utils; package it.fabioformosa.quartzmanager.controllers.utils;
import it.fabioformosa.quartzmanager.common.utils.DateUtils; import it.fabioformosa.quartzmanager.common.utils.DateUtils;
import it.fabioformosa.quartzmanager.dto.JobKeyDTO; import it.fabioformosa.quartzmanager.dto.*;
import it.fabioformosa.quartzmanager.dto.TriggerDTO;
import it.fabioformosa.quartzmanager.dto.TriggerKeyDTO;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -30,4 +28,50 @@ public class TriggerUtils {
.build(); .build();
} }
static public SimpleTriggerDTO getSimpleTriggerInstance(String triggerName, SimpleTriggerInputDTO simpleTriggerInputDTO){
return SimpleTriggerDTO.builder()
.description("simple trigger")
.repeatCount(simpleTriggerInputDTO.getRepeatCount())
.repeatInterval(simpleTriggerInputDTO.getRepeatInterval())
.endTime(DateUtils.getHoursFromNow(2L))
.finalFireTime(DateUtils.getHoursFromNow(2L))
.jobKeyDTO(JobKeyDTO.builder()
.group("defaultJobGroup")
.name("sampleJob")
.build())
.mayFireAgain(true)
.triggerKeyDTO(TriggerKeyDTO.builder()
.group("defaultTriggerGroup")
.name(triggerName)
.build())
.misfireInstruction(1)
.nextFireTime(DateUtils.getHoursFromNow(1L))
.priority(1)
.startTime(DateUtils.fromLocaleDateTimeToDate(LocalDateTime.now()))
.build();
}
static public SimpleTriggerDTO getSimpleTriggerInstance(String triggerName){
return SimpleTriggerDTO.builder()
.description("simple trigger")
.repeatCount(2)
.repeatInterval(1000L)
.endTime(DateUtils.getHoursFromNow(2L))
.finalFireTime(DateUtils.getHoursFromNow(2L))
.jobKeyDTO(JobKeyDTO.builder()
.group("defaultJobGroup")
.name("sampleJob")
.build())
.mayFireAgain(true)
.triggerKeyDTO(TriggerKeyDTO.builder()
.group("defaultTriggerGroup")
.name(triggerName)
.build())
.misfireInstruction(1)
.nextFireTime(DateUtils.getHoursFromNow(1L))
.priority(1)
.startTime(DateUtils.fromLocaleDateTimeToDate(LocalDateTime.now()))
.build();
}
} }

View File

@@ -0,0 +1,14 @@
package it.fabioformosa.quartzmanager.jobs;
import it.fabioformosa.quartzmanager.jobs.entities.LogRecord;
import it.fabioformosa.quartzmanager.jobs.entities.LogRecord.LogType;
import org.quartz.JobExecutionContext;
public class SampleJob extends AbstractLoggingJob {
@Override
public LogRecord doIt(JobExecutionContext jobExecutionContext) {
return new LogRecord(LogType.INFO, "Hello!");
}
}

View File

@@ -0,0 +1,81 @@
package it.fabioformosa.quartzmanager.services;
import it.fabioformosa.quartzmanager.common.utils.DateUtils;
import it.fabioformosa.quartzmanager.dto.*;
import it.fabioformosa.quartzmanager.exceptions.TriggerNotFoundException;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.springframework.core.convert.ConversionService;
import java.util.Date;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.MockitoAnnotations.openMocks;
class SimpleTriggerSchedulerServiceTest {
@InjectMocks
private SimpleTriggerSchedulerService simpleSchedulerService;
@Mock
private Scheduler scheduler;
@Mock
private ConversionService conversionService;
@BeforeEach
void setUp() {
openMocks(this);
}
@Test
void givenANotExistingTrigger_whenGetSimplerTriggerByNameIsCalled_thenThrowException() throws SchedulerException {
String not_existing_trigger = "not_existing_trigger";
Mockito.when(scheduler.getTrigger(any())).thenReturn(null);
Throwable throwable = Assertions.catchThrowable(() -> simpleSchedulerService.getSimpleTriggerByName(not_existing_trigger));
Assertions.assertThat(throwable).isInstanceOf(TriggerNotFoundException.class);
}
@Test
void givenASimpleTriggerCommandDTO_whenASimpleTriggerIsScheduled_thenATriggerDTOIsReturned() throws SchedulerException, ClassNotFoundException {
SimpleTriggerInputDTO triggerInputDTO = SimpleTriggerInputDTO.builder()
.startDate(new Date())
.repeatInterval(5000L).repeatCount(5)
.endDate(DateUtils.getHoursFromNow(1))
.build();
String simpleTriggerName = "simpleTrigger";
SimpleTriggerDTO expectedTriggerDTO = SimpleTriggerDTO.builder()
.startTime(triggerInputDTO.getStartDate())
.repeatInterval(1000)
.repeatCount(10)
.mayFireAgain(true)
.finalFireTime(triggerInputDTO.getEndDate())
.jobKeyDTO(JobKeyDTO.builder().name("MyJob").build())
.misfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW)
.triggerKeyDTO(TriggerKeyDTO.builder().name(simpleTriggerName).build())
.build();
Mockito.when(scheduler.scheduleJob(any(), any())).thenReturn(new Date());
Mockito.when(conversionService.convert(any(), eq(SimpleTriggerDTO.class))).thenReturn(expectedTriggerDTO);
SimpleTriggerCommandDTO simpleTriggerCommandDTO = SimpleTriggerCommandDTO.builder()
.triggerName(simpleTriggerName)
.simpleTriggerInputDTO(triggerInputDTO)
.build();
SimpleTriggerDTO simpleTrigger = simpleSchedulerService.scheduleSimpleTrigger("it.fabioformosa.quartzmanager.jobs.SampleJob", simpleTriggerCommandDTO);
Assertions.assertThat(simpleTrigger).isEqualTo(expectedTriggerDTO);
}
}

View File

@@ -0,0 +1,18 @@
app:
name: quartz-manager
quartz:
enabled: true
job:
frequency: 4000
repeatCount: 19
logging:
level:
org.springframework.boot.autoconfigure.security: INFO
it.fabioformosa: DEBUG
org.quartz: INFO
quartz-manager:
jobClass: it.fabioformosa.quartzmanager.jobs.SampleJob