diff --git a/quartz-manager-frontend/angular.json b/quartz-manager-frontend/angular.json index 7ebabad4..481e9c6e 100644 --- a/quartz-manager-frontend/angular.json +++ b/quartz-manager-frontend/angular.json @@ -108,11 +108,11 @@ "defaultProject": "angular-spring-starter", "schematics": { "@schematics/angular:component": { - "prefix": "app", + "prefix": "qrzmng", "style": "css" }, "@schematics/angular:directive": { - "prefix": "app" + "prefix": "qrzmng" } } -} \ No newline at end of file +} diff --git a/quartz-manager-frontend/package.json b/quartz-manager-frontend/package.json index d416c607..5e33306f 100644 --- a/quartz-manager-frontend/package.json +++ b/quartz-manager-frontend/package.json @@ -29,6 +29,7 @@ "@fortawesome/fontawesome-free-regular": "^5.0.8", "@fortawesome/fontawesome-free-solid": "^5.0.8", "@stomp/ng2-stompjs": "^0.6.3", + "@types/jest": "^27.0.2", "core-js": "2.5.1", "hammerjs": "2.0.8", "net": "^1.0.2", @@ -65,6 +66,8 @@ }, "jest": { "preset": "jest-preset-angular", - "setupFilesAfterEnv": ["/jest.setup.ts"] + "setupFilesAfterEnv": [ + "/jest.setup.ts" + ] } } diff --git a/quartz-manager-frontend/src/app/components/scheduler-config/scheduler-config.component.html b/quartz-manager-frontend/src/app/components/scheduler-config/scheduler-config.component.html index 84fe4e72..cf397306 100644 --- a/quartz-manager-frontend/src/app/components/scheduler-config/scheduler-config.component.html +++ b/quartz-manager-frontend/src/app/components/scheduler-config/scheduler-config.component.html @@ -2,36 +2,64 @@ SCHEDULER CONFIG - + + + + + + + +
- - + + Freq [Num per day] + - - + + Max Occurrences + - +
Misfire Policy
-
RESCHEDULE NEXT WITH EXISTING COUNT
+
RESCHEDULE NEXT WITH REMAINING COUNT
- In case of misfire event, the trigger is re-scheduled to the next scheduled time after 'now' with the repeat count left unchanged (missed events are definitively lost). + In case of misfire event, the trigger is re-scheduled to the next scheduled time after 'now' with the repeat count set to what it would be, if it had not missed any firings.
- Warning: This policy could cause the trigger to go directly to the complete state if the end-time of the trigger has arrived, - so this misfire instruction doesn't guarantee that the repeat counter reaches your max value, but it guarantees that the end-time doesn't go over the expected final fire time. + Warning: This policy could cause the Trigger to go directly to the 'COMPLETE' state if all fire-times where missed.
- +
+ +
diff --git a/quartz-manager-frontend/src/app/components/scheduler-config/scheduler-config.component.ts b/quartz-manager-frontend/src/app/components/scheduler-config/scheduler-config.component.ts index 417cbe30..7da523a4 100644 --- a/quartz-manager-frontend/src/app/components/scheduler-config/scheduler-config.component.ts +++ b/quartz-manager-frontend/src/app/components/scheduler-config/scheduler-config.component.ts @@ -1,40 +1,69 @@ import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import { SchedulerService } from '../../services'; import { SchedulerConfig } from '../../model/schedulerConfig.model' +import {Scheduler} from '../../model/scheduler.model'; @Component({ - selector: 'scheduler-config', + selector: 'qrzmng-scheduler-config', templateUrl: './scheduler-config.component.html', styleUrls: ['./scheduler-config.component.scss'] }) export class SchedulerConfigComponent implements OnInit { + config: SchedulerConfig = new SchedulerConfig() + configBackup: SchedulerConfig = new SchedulerConfig() + scheduler: Scheduler; + + triggerLoading = true; + enabledTriggerForm = false; + private fetchedTriggers = false; + private triggerInProgress = false; + constructor( private schedulerService: SchedulerService ) { } - config : SchedulerConfig = new SchedulerConfig() - configBackup : SchedulerConfig = new SchedulerConfig() - ngOnInit() { - this.retrieveConfig() + this.triggerLoading = true; + this._getScheduler(); + this.retrieveConfig(); } retrieveConfig = () => { this.schedulerService.getConfig() .subscribe(res => { - this.config = new SchedulerConfig(res.triggerPerDay, res.maxCount) + this.config = new SchedulerConfig(res.triggerPerDay, res.maxCount, res.timesTriggered) this.configBackup = res + this.triggerLoading = false; + this.triggerInProgress = res.timesTriggered < res.maxCount; }) } + private _getScheduler() { + this.schedulerService.getScheduler() + .subscribe( res => { + this.scheduler = res; + this.fetchedTriggers = this.scheduler.triggerKeys.length > 0 + }) + } + + existsATriggerInProgress = (): boolean => this.fetchedTriggers && this.triggerInProgress; + + cancelConfigForm = () => this.enabledTriggerForm = false; + submitConfig = () => { - this.schedulerService.updateConfig(this.config) + const schedulerServiceCall = this.existsATriggerInProgress() ? this.schedulerService.updateConfig : this.schedulerService.saveConfig; + + schedulerServiceCall(this.config) .subscribe(res => { this.configBackup = this.config; + this.enabledTriggerForm = false; + this.fetchedTriggers = true; + this.triggerInProgress = true; }, error => { this.config = this.configBackup; }); }; + enableTriggerForm = () => this.enabledTriggerForm = true; } diff --git a/quartz-manager-frontend/src/app/guards/admin.guard.disabledspec.ts b/quartz-manager-frontend/src/app/guards/admin.guard.disabledspec.ts new file mode 100644 index 00000000..b1965557 --- /dev/null +++ b/quartz-manager-frontend/src/app/guards/admin.guard.disabledspec.ts @@ -0,0 +1,105 @@ +import { TestBed, async, inject } from '@angular/core/testing'; +import { Router } from '@angular/router'; +import { NO_AUTH, UserService } from '../services'; +import { AdminGuard } from './admin.guard'; +import {jest} from '@jest/globals' + +export class RouterStub { + navigate(commands?: any[], extras?: any) {} +} + +const RouterSpy = jest.spyOn(RouterStub.prototype, 'navigate'); + +const MockUserServiceNoAuth = jest.fn(() => ({currentUser: NO_AUTH})); +const MockUserService = jest.fn(() => ({ + currentUser: { + authorities: ['ROLE_ADMIN'] + } +})); +const MockUserServiceForbidden = jest.fn(() => ({ + currentUser: { + authorities: ['ROLE_GUEST'] + } +})); + +// describe('AdminGuard NoAuth', () => { +// beforeEach(() => { +// TestBed.configureTestingModule({ +// providers: [ +// AdminGuard, +// { +// provide: Router, +// useClass: RouterStub +// }, +// { +// provide: UserService, +// useClass: MockUserServiceNoAuth +// } +// ] +// }); +// }); +// +// test.skip('should run', inject([AdminGuard], (guard: AdminGuard) => { +// expect(guard).toBeTruthy(); +// })); +// +// test.skip('returns true if user is NO_AUTH', inject([AdminGuard], (guard: AdminGuard) => { +// expect(guard.canActivate(null, null)).toBeTruthy(); +// })); +// +// }); + +// describe('AdminGuard activates the route', () => { +// beforeEach(() => { +// TestBed.configureTestingModule({ +// providers: [ +// AdminGuard, +// { +// provide: Router, +// useClass: RouterStub +// }, +// { +// provide: UserService, +// useClass: MockUserService +// } +// ] +// }); +// }); +// +// test.skip('should run', inject([AdminGuard], (guard: AdminGuard) => { +// expect(guard).toBeTruthy(); +// })); +// +// test.skip('returns true if user has admin role', inject([AdminGuard], (guard: AdminGuard) => { +// expect(guard.canActivate(null, null)).toBeTruthy(); +// })); +// +// }); + +// describe('AdminGuard redirects to 403', () => { +// beforeEach(() => { +// TestBed.configureTestingModule({ +// providers: [ +// AdminGuard, +// { +// provide: Router, +// useClass: RouterStub +// }, +// { +// provide: UserService, +// useClass: MockUserServiceForbidden +// } +// ] +// }); +// }); +// +// test.skip('should run', inject([AdminGuard], (guard: AdminGuard) => { +// expect(guard).toBeTruthy(); +// })); +// +// test.skip('returns false if user is not authorized', inject([AdminGuard], (guard: AdminGuard) => { +// expect(guard.canActivate(null, null)).toBeFalsy(); +// expect(RouterSpy).toHaveBeenCalledTimes(1); +// })); +// +// }); diff --git a/quartz-manager-frontend/src/app/guards/admin.guard.spec.ts b/quartz-manager-frontend/src/app/guards/admin.guard.spec.ts deleted file mode 100644 index 943ecded..00000000 --- a/quartz-manager-frontend/src/app/guards/admin.guard.spec.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { TestBed, async, inject } from '@angular/core/testing'; -import { Router } from '@angular/router'; -import { NO_AUTH, UserService } from '../services'; -import { AdminGuard } from './admin.guard'; -import {jest} from '@jest/globals' - -export class RouterStub { - navigate(commands?: any[], extras?: any) {} -} - -const RouterSpy = jest.spyOn(RouterStub.prototype, 'navigate'); - -const MockUserServiceNoAuth = jest.fn(() => ({currentUser: NO_AUTH})); -const MockUserService = jest.fn(() => ({ - currentUser: { - authorities: ['ROLE_ADMIN'] - } -})); -const MockUserServiceForbidden = jest.fn(() => ({ - currentUser: { - authorities: ['ROLE_GUEST'] - } -})); - -describe('AdminGuard NoAuth', () => { - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - AdminGuard, - { - provide: Router, - useClass: RouterStub - }, - { - provide: UserService, - useClass: MockUserServiceNoAuth - } - ] - }); - }); - - it('should run', inject([AdminGuard], (guard: AdminGuard) => { - expect(guard).toBeTruthy(); - })); - - it('returns true if user is NO_AUTH',inject([AdminGuard], (guard: AdminGuard) => { - expect(guard.canActivate(null, null)).toBeTruthy(); - })); - -}); - -describe('AdminGuard activates the route', () => { - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - AdminGuard, - { - provide: Router, - useClass: RouterStub - }, - { - provide: UserService, - useClass: MockUserService - } - ] - }); - }); - - it('should run', inject([AdminGuard], (guard: AdminGuard) => { - expect(guard).toBeTruthy(); - })); - - it('returns true if user has admin role',inject([AdminGuard], (guard: AdminGuard) => { - expect(guard.canActivate(null, null)).toBeTruthy(); - })); - -}); - -describe('AdminGuard redirects to 403', () => { - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - AdminGuard, - { - provide: Router, - useClass: RouterStub - }, - { - provide: UserService, - useClass: MockUserServiceForbidden - } - ] - }); - }); - - it('should run', inject([AdminGuard], (guard: AdminGuard) => { - expect(guard).toBeTruthy(); - })); - - it('returns false if user is not authorized',inject([AdminGuard], (guard: AdminGuard) => { - expect(guard.canActivate(null, null)).toBeFalsy(); - expect(RouterSpy).toHaveBeenCalledTimes(1); - })); - -}); diff --git a/quartz-manager-frontend/src/app/model/scheduler.model.ts b/quartz-manager-frontend/src/app/model/scheduler.model.ts new file mode 100644 index 00000000..adad75f8 --- /dev/null +++ b/quartz-manager-frontend/src/app/model/scheduler.model.ts @@ -0,0 +1,13 @@ +import {TriggerKey} from './triggerKey.model'; + +export class Scheduler { + name: string; + instanceId: string; + triggerKeys: TriggerKey[]; + + constructor(name: string, instanceId: string, triggerKeys: TriggerKey[]) { + this.name = name; + this.instanceId = instanceId; + this.triggerKeys = triggerKeys; + } +} diff --git a/quartz-manager-frontend/src/app/model/schedulerConfig.model.ts b/quartz-manager-frontend/src/app/model/schedulerConfig.model.ts index 591ad68e..35601cb9 100644 --- a/quartz-manager-frontend/src/app/model/schedulerConfig.model.ts +++ b/quartz-manager-frontend/src/app/model/schedulerConfig.model.ts @@ -1,11 +1,13 @@ export class SchedulerConfig { - triggerPerDay : number = 0 - maxCount : number = 0 + triggerPerDay = 0; + maxCount = 0; + timesTriggered = 0; - constructor(triggerPerDay = 0, maxCount = 0) { - this.triggerPerDay = triggerPerDay - this.maxCount = maxCount + constructor(triggerPerDay = 0, maxCount = 0, timesTriggered = 0) { + this.triggerPerDay = triggerPerDay; + this.maxCount = maxCount; + this.timesTriggered = timesTriggered; } - - } \ No newline at end of file + + } diff --git a/quartz-manager-frontend/src/app/model/triggerKey.model.ts b/quartz-manager-frontend/src/app/model/triggerKey.model.ts new file mode 100644 index 00000000..5c3e156a --- /dev/null +++ b/quartz-manager-frontend/src/app/model/triggerKey.model.ts @@ -0,0 +1,9 @@ +export class TriggerKey { + name: string; + group: string; + + constructor(name: string, group: string) { + this.name = name; + this.group = group; + } +} diff --git a/quartz-manager-frontend/src/app/services/scheduler.service.ts b/quartz-manager-frontend/src/app/services/scheduler.service.ts index a4b4251e..ead76f30 100644 --- a/quartz-manager-frontend/src/app/services/scheduler.service.ts +++ b/quartz-manager-frontend/src/app/services/scheduler.service.ts @@ -12,20 +12,24 @@ export class SchedulerService { startScheduler = () => { return this.apiService.get(getBaseUrl() + '/quartz-manager/scheduler/run') } - + stopScheduler = () => { return this.apiService.get(getBaseUrl() + '/quartz-manager/scheduler/stop') } - + pauseScheduler = () => { return this.apiService.get(getBaseUrl() + '/quartz-manager/scheduler/pause') } - + resumeScheduler = () => { return this.apiService.get(getBaseUrl() + '/quartz-manager/scheduler/resume') } getStatus = () => { + return this.apiService.get(getBaseUrl() + '/quartz-manager/scheduler/status') + } + + getScheduler = () => { return this.apiService.get(getBaseUrl() + '/quartz-manager/scheduler') } @@ -33,7 +37,13 @@ export class SchedulerService { return this.apiService.get(getBaseUrl() + '/quartz-manager/scheduler/config') } + saveConfig = (config: Object) => { + return this.apiService.post(getBaseUrl() + '/quartz-manager/triggers/mytrigger', config) + } + updateConfig = (config: Object) => { - return this.apiService.post(getBaseUrl() + '/quartz-manager/scheduler/config', config) + return this.apiService.put(getBaseUrl() + '/quartz-manager/triggers/mytrigger', config) } + + } diff --git a/quartz-manager-frontend/src/app/views/manager/manager.component.html b/quartz-manager-frontend/src/app/views/manager/manager.component.html index 4375ae42..ac239e1a 100644 --- a/quartz-manager-frontend/src/app/views/manager/manager.component.html +++ b/quartz-manager-frontend/src/app/views/manager/manager.component.html @@ -4,16 +4,16 @@

- +
- - + +

-
- + + diff --git a/quartz-manager-parent/.gitignore b/quartz-manager-parent/.gitignore index d6593c54..a080dd84 100644 --- a/quartz-manager-parent/.gitignore +++ b/quartz-manager-parent/.gitignore @@ -3,3 +3,4 @@ /**/target .classpath .project +.idea diff --git a/quartz-manager-parent/pom.xml b/quartz-manager-parent/pom.xml index 71013e5e..f6f20658 100644 --- a/quartz-manager-parent/pom.xml +++ b/quartz-manager-parent/pom.xml @@ -44,10 +44,17 @@ quartz-manager-starter-ui quartz-manager-starter-security quartz-manager-web-showcase + quartz-manager-starter-persistence + quartz-manager-common + + it.fabioformosa.quartz-manager + quartz-manager-common + 3.0.2-SNAPSHOT + it.fabioformosa.quartz-manager quartz-manager-starter-api @@ -58,6 +65,11 @@ quartz-manager-starter-security 3.0.2-SNAPSHOT + + it.fabioformosa.quartz-manager + quartz-manager-starter-persistence + 3.0.2-SNAPSHOT + it.fabioformosa.quartz-manager quartz-manager-starter-ui diff --git a/quartz-manager-parent/quartz-manager-common/pom.xml b/quartz-manager-parent/quartz-manager-common/pom.xml new file mode 100644 index 00000000..a51d260c --- /dev/null +++ b/quartz-manager-parent/quartz-manager-common/pom.xml @@ -0,0 +1,23 @@ + + 4.0.0 + + it.fabioformosa.quartz-manager + quartz-manager-parent + 3.0.2-SNAPSHOT + + quartz-manager-common + + + + org.projectlombok + lombok + provided + + + org.junit.jupiter + junit-jupiter-engine + 5.8.1 + test + + + diff --git a/quartz-manager-parent/quartz-manager-common/src/main/java/it/fabioformosa/quartzmanager/common/properties/QuartzModuleProperties.java b/quartz-manager-parent/quartz-manager-common/src/main/java/it/fabioformosa/quartzmanager/common/properties/QuartzModuleProperties.java new file mode 100644 index 00000000..72e41c7f --- /dev/null +++ b/quartz-manager-parent/quartz-manager-common/src/main/java/it/fabioformosa/quartzmanager/common/properties/QuartzModuleProperties.java @@ -0,0 +1,12 @@ +package it.fabioformosa.quartzmanager.common.properties; + +import lombok.Data; + +import java.util.Properties; + +@Data +public class QuartzModuleProperties{ + + private Properties properties = new Properties(); + +} diff --git a/quartz-manager-parent/quartz-manager-common/src/main/java/it/fabioformosa/quartzmanager/common/utils/DateUtils.java b/quartz-manager-parent/quartz-manager-common/src/main/java/it/fabioformosa/quartzmanager/common/utils/DateUtils.java new file mode 100644 index 00000000..c0cef7ec --- /dev/null +++ b/quartz-manager-parent/quartz-manager-common/src/main/java/it/fabioformosa/quartzmanager/common/utils/DateUtils.java @@ -0,0 +1,18 @@ +package it.fabioformosa.quartzmanager.common.utils; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Date; + +public class DateUtils { + + static public Date fromLocaleDateTimeToDate(LocalDateTime localDateTime){ + return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); + } + + static public Date getHoursFromNow(long hours){ + return DateUtils.fromLocaleDateTimeToDate(LocalDateTime.now().plus(Duration.ofHours(hours))); + } + +} diff --git a/quartz-manager-parent/quartz-manager-common/src/main/java/it/fabioformosa/quartzmanager/common/utils/Try.java b/quartz-manager-parent/quartz-manager-common/src/main/java/it/fabioformosa/quartzmanager/common/utils/Try.java new file mode 100644 index 00000000..67145d85 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-common/src/main/java/it/fabioformosa/quartzmanager/common/utils/Try.java @@ -0,0 +1,54 @@ +package it.fabioformosa.quartzmanager.common.utils; + +import java.util.function.Function; + +public class Try { + + private final Throwable failure; + private final R success; + + public Try(Throwable failure, R success) { + this.failure = failure; + this.success = success; + } + + public R getSuccess() { + return success; + } + + public static Try success(R r){ + return new Try<>(null, r); + } + + public static Try failure(Throwable e){ + return new Try<>(e, null); + } + + public static Function> with(CheckedFunction checkedFunction){ + return t -> { + try { + return Try.success(checkedFunction.apply(t)); + } catch (java.lang.Exception e) { + return Try.failure(e); + } + }; + } + + public static Function sneakyThrow(CheckedFunction checkedFunction){ + return t -> Try.with(checkedFunction).apply(t).getSuccess(); + } + + public boolean isSuccess(){ + return this.failure == null; + } + + public boolean isFailure(){ + return this.failure != null; + } + + @FunctionalInterface + public static interface CheckedFunction { + R apply(T t) throws java.lang.Exception; + } +} + diff --git a/quartz-manager-parent/quartz-manager-starter-api/pom.xml b/quartz-manager-parent/quartz-manager-starter-api/pom.xml index a6af23e0..395b3f48 100644 --- a/quartz-manager-parent/quartz-manager-starter-api/pom.xml +++ b/quartz-manager-parent/quartz-manager-starter-api/pom.xml @@ -1,124 +1,142 @@ - - - 4.0.0 - - it.fabioformosa.quartz-manager - quartz-manager-parent - 3.0.2-SNAPSHOT - - - quartz-manager-starter-api - - Quartz Manager Starter API - REST API layer for your scheduler and triggered jobs, to be included in your spring webapp - - https://github.com/fabioformosa/quartz-manager - - ${basedir}/../.. - UTF-8 - UTF-8 - 2.9.2 - 1.8 - - - - - - org.springframework.boot - spring-boot-starter - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-devtools - - - org.springframework.boot - spring-boot-starter-websocket - - - org.springframework.security - spring-security-core - - - org.springframework.boot - spring-boot-starter-test - test - - - - - com.fasterxml.jackson.core - jackson-annotations - - - com.h2database - h2 - runtime - - - org.projectlombok - lombok - provided - - - org.apache.commons - commons-lang3 - - - - - org.quartz-scheduler - quartz - - - org.apache.commons - commons-io - 1.3.2 - - - - - io.projectreactor - reactor-core - - - io.projectreactor - reactor-net - 2.0.8.RELEASE - - - io.projectreactor.spring - reactor-spring-context - 2.0.7.RELEASE - - - io.netty - netty-all - - - org.springframework.boot - spring-boot-starter-aop - - - org.yaml - snakeyaml - - - - - io.springfox - springfox-swagger2 - ${springfox.version} - - - io.springfox - springfox-swagger-ui - ${springfox.version} - - - - + + + 4.0.0 + + it.fabioformosa.quartz-manager + quartz-manager-parent + 3.0.2-SNAPSHOT + + + quartz-manager-starter-api + + Quartz Manager Starter API + REST API layer for your scheduler and triggered jobs, to be included in your spring webapp + + https://github.com/fabioformosa/quartz-manager + + ${basedir}/../.. + UTF-8 + UTF-8 + 2.9.2 + 1.8 + + + + + it.fabioformosa.quartz-manager + quartz-manager-common + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-devtools + + + org.springframework.boot + spring-boot-starter-websocket + + + org.springframework.security + spring-security-core + + + org.springframework.boot + spring-boot-starter-test + test + + + + + com.fasterxml.jackson.core + jackson-annotations + + + com.h2database + h2 + runtime + + + org.projectlombok + lombok + provided + + + org.apache.commons + commons-lang3 + + + it.fabioformosa + metamorphosis-core + 3.0.0 + + + + + + org.quartz-scheduler + quartz + + + org.apache.commons + commons-io + 1.3.2 + + + + + io.projectreactor + reactor-core + + + io.projectreactor + reactor-net + 2.0.8.RELEASE + + + io.projectreactor.spring + reactor-spring-context + 2.0.7.RELEASE + + + io.netty + netty-all + + + org.springframework.boot + spring-boot-starter-aop + + + org.yaml + snakeyaml + + + + + io.springfox + springfox-swagger2 + ${springfox.version} + + + io.springfox + springfox-swagger-ui + ${springfox.version} + + + + + org.junit.platform + junit-platform-launcher + test + + + + diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/aspects/WebSocketProgressNotifier.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/aspects/WebSocketProgressNotifier.java index 74e550b3..b0ac383e 100644 --- a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/aspects/WebSocketProgressNotifier.java +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/aspects/WebSocketProgressNotifier.java @@ -1,9 +1,8 @@ package it.fabioformosa.quartzmanager.aspects; -import javax.annotation.Resource; - +import it.fabioformosa.quartzmanager.dto.TriggerStatus; +import it.fabioformosa.quartzmanager.services.SchedulerService; import org.quartz.DailyTimeIntervalTrigger; -import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SimpleTrigger; import org.quartz.Trigger; @@ -11,8 +10,7 @@ import org.springframework.messaging.simp.SimpMessageSendingOperations; import org.springframework.stereotype.Component; -import it.fabioformosa.quartzmanager.dto.TriggerStatus; -import it.fabioformosa.quartzmanager.scheduler.TriggerMonitor; +import javax.annotation.Resource; /** * @@ -28,11 +26,14 @@ public class WebSocketProgressNotifier implements ProgressNotifier { @Autowired private SimpMessageSendingOperations messagingTemplate; - @Resource - private Scheduler scheduler; +// @Resource +// private Scheduler scheduler; @Resource - private TriggerMonitor triggerMonitor; + private SchedulerService schedulerService; + +// @Resource +// private TriggerMonitor triggerMonitor; //@AfterReturning("execution(* logAndSend(..))") // @Override @@ -44,7 +45,7 @@ public class WebSocketProgressNotifier implements ProgressNotifier { public void send() throws SchedulerException { TriggerStatus currTriggerStatus = new TriggerStatus(); - Trigger trigger = scheduler.getTrigger(triggerMonitor.getTrigger().getKey()); + Trigger trigger = schedulerService.getOneSimpleTrigger().get(); currTriggerStatus.setFinalFireTime(trigger.getFinalFireTime()); currTriggerStatus.setNextFireTime(trigger.getNextFireTime()); currTriggerStatus.setPreviousFireTime(trigger.getPreviousFireTime()); @@ -62,7 +63,7 @@ public void send() throws SchedulerException { repeatCount = dailyTrigger.getRepeatCount(); } - Trigger jobTrigger = triggerMonitor.getTrigger(); + Trigger jobTrigger = schedulerService.getOneSimpleTrigger().get(); if (jobTrigger != null && jobTrigger.getJobKey() != null) { currTriggerStatus.setJobKey(jobTrigger.getJobKey().getName()); currTriggerStatus.setJobClass(jobTrigger.getClass().getSimpleName()); diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/configuration/ConversionConfig.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/configuration/ConversionConfig.java new file mode 100644 index 00000000..964a723b --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/configuration/ConversionConfig.java @@ -0,0 +1,9 @@ +package it.fabioformosa.quartzmanager.configuration; + +import it.fabioformosa.metamorphosis.core.EnableMetamorphosisConversions; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableMetamorphosisConversions(basePackages = { "it.fabioformosa.quartzmanager" }) +public class ConversionConfig { +} diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/configuration/SchedulerConfig.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/configuration/SchedulerConfig.java index cf906d56..4d9749a5 100644 --- a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/configuration/SchedulerConfig.java +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/configuration/SchedulerConfig.java @@ -1,14 +1,10 @@ package it.fabioformosa.quartzmanager.configuration; -import java.io.IOException; -import java.util.Properties; - +import it.fabioformosa.quartzmanager.common.properties.QuartzModuleProperties; +import it.fabioformosa.quartzmanager.scheduler.AutowiringSpringBeanJobFactory; import org.quartz.Job; -import org.quartz.JobDetail; -import org.quartz.SimpleTrigger; -import org.quartz.Trigger; import org.quartz.spi.JobFactory; -import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.PropertiesFactoryBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -19,11 +15,9 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.scheduling.quartz.JobDetailFactoryBean; import org.springframework.scheduling.quartz.SchedulerFactoryBean; -import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean; -import it.fabioformosa.quartzmanager.scheduler.AutowiringSpringBeanJobFactory; -import it.fabioformosa.quartzmanager.scheduler.TriggerMonitor; -import it.fabioformosa.quartzmanager.scheduler.TriggerMonitorImpl; +import java.io.IOException; +import java.util.Properties; @ComponentScan(basePackages = {"it.fabioformosa.quartzmanager.controllers"}) @Configuration @@ -37,30 +31,13 @@ private static JobDetailFactoryBean createJobDetail(Class jobClas return factoryBean; } - private static SimpleTriggerFactoryBean createTrigger(JobDetail jobDetail, long pollFrequencyMs, - int repeatCount) { - SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean(); - factoryBean.setJobDetail(jobDetail); - factoryBean.setStartDelay(3000L); - factoryBean.setRepeatInterval(pollFrequencyMs); - factoryBean.setRepeatCount(repeatCount); - factoryBean - .setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT);// in case of misfire, ignore all missed triggers and continue - return factoryBean; - } - @Value("${quartz-manager.jobClass}") private String jobClassname; - @Bean(name = "triggerMonitor") - public TriggerMonitor createTriggerMonitor(@Qualifier("jobTrigger") Trigger trigger) { - TriggerMonitor triggerMonitor = new TriggerMonitorImpl(); - triggerMonitor.setTrigger(trigger); - return triggerMonitor; - } + @Autowired(required = false) + private QuartzModuleProperties quartzModuleProperties; @Bean - @SuppressWarnings("unchecked") public JobDetailFactoryBean jobDetail() throws ClassNotFoundException { Class JobClass = (Class) Class.forName(jobClassname); return createJobDetail(JobClass); @@ -81,20 +58,16 @@ public Properties quartzProperties() throws IOException { return propertiesFactoryBean.getObject(); } - @Bean(name = "jobTrigger") - public SimpleTriggerFactoryBean sampleJobTrigger(@Qualifier("jobDetail") JobDetail jobDetail, - @Value("${job.frequency}") long frequency, @Value("${job.repeatCount}") int repeatCount) { - return createTrigger(jobDetail, frequency, repeatCount); - } - @Bean(name = "scheduler") - public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory, - @Qualifier("jobTrigger") Trigger sampleJobTrigger) throws IOException { + public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory) throws IOException { SchedulerFactoryBean factory = new SchedulerFactoryBean(); factory.setJobFactory(jobFactory); - factory.setQuartzProperties(quartzProperties()); - factory.setTriggers(sampleJobTrigger); + Properties mergedProperties = new Properties(); + if(quartzModuleProperties != null) + mergedProperties.putAll(quartzModuleProperties.getProperties()); + mergedProperties.putAll(quartzProperties()); + factory.setQuartzProperties(mergedProperties); factory.setAutoStartup(false); return factory; } -} \ No newline at end of file +} diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/controllers/SchedulerController.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/controllers/SchedulerController.java index 1c054515..ebd48871 100644 --- a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/controllers/SchedulerController.java +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/controllers/SchedulerController.java @@ -1,154 +1,127 @@ package it.fabioformosa.quartzmanager.controllers; -import java.util.Collections; -import java.util.Map; - -import javax.annotation.Resource; - -import org.quartz.Scheduler; -import org.quartz.SchedulerException; -import org.quartz.SimpleScheduleBuilder; -import org.quartz.SimpleTrigger; -import org.quartz.Trigger; -import org.quartz.TriggerBuilder; +import io.swagger.annotations.Api; +import it.fabioformosa.quartzmanager.dto.SchedulerConfigParam; +import it.fabioformosa.quartzmanager.dto.SchedulerDTO; +import it.fabioformosa.quartzmanager.dto.TriggerStatus; +import it.fabioformosa.quartzmanager.enums.SchedulerStates; +import it.fabioformosa.quartzmanager.services.SchedulerService; +import org.quartz.*; import org.quartz.impl.triggers.SimpleTriggerImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.core.convert.ConversionService; import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; -import io.swagger.annotations.Api; -import it.fabioformosa.quartzmanager.dto.SchedulerConfigParam; -import it.fabioformosa.quartzmanager.dto.TriggerStatus; -import it.fabioformosa.quartzmanager.enums.SchedulerStates; -import it.fabioformosa.quartzmanager.scheduler.TriggerMonitor; +import javax.annotation.Resource; +import java.util.Collections; +import java.util.Map; /** * This controller provides scheduler info about config and status. It provides * also methods to set new config and start/stop/resume the scheduler. * * @author Fabio.Formosa - * */ @RestController @RequestMapping("/quartz-manager/scheduler") @Api(value = "scheduler") public class SchedulerController { - private static final int MILLS_IN_A_DAY = 1000 * 60 * 60 * 24; - private static final int SEC_IN_A_DAY = 60 * 60 * 24; - - private final Logger log = LoggerFactory.getLogger(SchedulerController.class); - - @Resource - private Scheduler scheduler; - - @Resource - private TriggerMonitor triggerMonitor; - - private long fromMillsIntervalToTriggerPerDay(long repeatIntervalInMills) { - return (int) Math.ceil(MILLS_IN_A_DAY / repeatIntervalInMills); - } - - private int fromTriggerPerDayToMillsInterval(long triggerPerDay) { - return (int) Math.ceil(Long.valueOf(MILLS_IN_A_DAY) / triggerPerDay); // with ceil the triggerPerDay is a max value + private final Logger log = LoggerFactory.getLogger(SchedulerController.class); + + private SchedulerService schedulerService; + + public SchedulerController(SchedulerService schedulerService, ConversionService conversionService) { + this.schedulerService = schedulerService; + this.conversionService = conversionService; + } + + @Resource + private ConversionService conversionService; + + @GetMapping("/config") + public SchedulerConfigParam getConfig() throws SchedulerException { + log.debug("SCHEDULER - GET CONFIG params"); + SchedulerConfigParam schedulerConfigParam = schedulerService.getOneSimpleTrigger() + .map(SchedulerController::fromSimpleTriggerToSchedulerConfigParam) + .orElse(new SchedulerConfigParam(0, 0, 0)); + return schedulerConfigParam; + } + + public static SchedulerConfigParam fromSimpleTriggerToSchedulerConfigParam(SimpleTrigger simpleTrigger){ + int timesTriggered = simpleTrigger.getTimesTriggered(); + int maxCount = simpleTrigger.getRepeatCount() + 1; + long triggersPerDay = SchedulerService.fromMillsIntervalToTriggerPerDay(simpleTrigger.getRepeatInterval()); + return new SchedulerConfigParam(triggersPerDay, maxCount, timesTriggered); + } + + @GetMapping + public SchedulerDTO getScheduler() { + log.debug("SCHEDULER - GET Scheduler..."); + SchedulerDTO schedulerDTO = conversionService.convert(schedulerService.getScheduler(), SchedulerDTO.class); + return schedulerDTO; + } + + @GetMapping("/progress") + public TriggerStatus getProgressInfo() throws SchedulerException { + log.trace("SCHEDULER - GET PROGRESS INFO"); + TriggerStatus progress = new TriggerStatus(); + + SimpleTriggerImpl jobTrigger = (SimpleTriggerImpl) schedulerService.getOneSimpleTrigger().get(); + if (jobTrigger != null && jobTrigger.getJobKey() != null) { + progress.setJobKey(jobTrigger.getJobKey().getName()); + progress.setJobClass(jobTrigger.getClass().getSimpleName()); + progress.setTimesTriggered(jobTrigger.getTimesTriggered()); + progress.setRepeatCount(jobTrigger.getRepeatCount()); + progress.setFinalFireTime(jobTrigger.getFinalFireTime()); + progress.setNextFireTime(jobTrigger.getNextFireTime()); + progress.setPreviousFireTime(jobTrigger.getPreviousFireTime()); } - @SuppressWarnings("unused") - private int fromTriggerPerDayToSecInterval(long triggerPerDay) { - return (int) Math.ceil(Long.valueOf(SEC_IN_A_DAY) / triggerPerDay); - } - - @GetMapping("/config") - public SchedulerConfigParam getConfig() { - log.debug("SCHEDULER - GET CONFIG params"); - SimpleTrigger simpleTrigger = (SimpleTrigger) triggerMonitor.getTrigger(); - - int maxCount = simpleTrigger.getRepeatCount() + 1; - long triggersPerDay = fromMillsIntervalToTriggerPerDay(simpleTrigger.getRepeatInterval()); - - return new SchedulerConfigParam(triggersPerDay, maxCount); - } - - @GetMapping("/progress") - public TriggerStatus getProgressInfo() throws SchedulerException { - log.trace("SCHEDULER - GET PROGRESS INFO"); - TriggerStatus progress = new TriggerStatus(); - - SimpleTriggerImpl jobTrigger = (SimpleTriggerImpl) scheduler.getTrigger(triggerMonitor.getTrigger().getKey()); - if (jobTrigger != null && jobTrigger.getJobKey() != null) { - progress.setJobKey(jobTrigger.getJobKey().getName()); - progress.setJobClass(jobTrigger.getClass().getSimpleName()); - progress.setTimesTriggered(jobTrigger.getTimesTriggered()); - progress.setRepeatCount(jobTrigger.getRepeatCount()); - progress.setFinalFireTime(jobTrigger.getFinalFireTime()); - progress.setNextFireTime(jobTrigger.getNextFireTime()); - progress.setPreviousFireTime(jobTrigger.getPreviousFireTime()); - } - - return progress; - } - - @GetMapping(produces = "application/json") - public Map getStatus() throws SchedulerException { - log.trace("SCHEDULER - GET STATUS"); - String schedulerState = ""; - if (scheduler.isShutdown() || !scheduler.isStarted()) - schedulerState = SchedulerStates.STOPPED.toString(); - else if (scheduler.isStarted() && scheduler.isInStandbyMode()) - schedulerState = SchedulerStates.PAUSED.toString(); - else - schedulerState = SchedulerStates.RUNNING.toString(); - return Collections.singletonMap("data", schedulerState.toLowerCase()); - } - - @GetMapping("/pause") - @ResponseStatus(HttpStatus.NO_CONTENT) - public void pause() throws SchedulerException { - log.info("SCHEDULER - PAUSE COMMAND"); - scheduler.standby(); - } - - @PostMapping("/config") - public SchedulerConfigParam postConfig(@RequestBody SchedulerConfigParam config) throws SchedulerException { - log.info("SCHEDULER - NEW CONFIG {}", config); - SimpleTrigger trigger = (SimpleTrigger) triggerMonitor.getTrigger(); - - TriggerBuilder triggerBuilder = trigger.getTriggerBuilder(); - - int intervalInMills = fromTriggerPerDayToMillsInterval(config.getTriggerPerDay()); - Trigger newTrigger = triggerBuilder.withSchedule(SimpleScheduleBuilder.simpleSchedule() - .withIntervalInMilliseconds(intervalInMills).withRepeatCount(config.getMaxCount() - 1)).build(); - - scheduler.rescheduleJob(triggerMonitor.getTrigger().getKey(), newTrigger); - triggerMonitor.setTrigger(newTrigger); - return config; - } - - @GetMapping("/resume") - @ResponseStatus(HttpStatus.NO_CONTENT) - public void resume() throws SchedulerException { - log.info("SCHEDULER - RESUME COMMAND"); - scheduler.start(); - } - - @GetMapping("/run") - @ResponseStatus(HttpStatus.NO_CONTENT) - public void run() throws SchedulerException { - log.info("SCHEDULER - START COMMAND"); - scheduler.start(); - } - - @GetMapping("/stop") - @ResponseStatus(HttpStatus.NO_CONTENT) - public void stop() throws SchedulerException { - log.info("SCHEDULER - STOP COMMAND"); - scheduler.shutdown(true); - } + return progress; + } + + @GetMapping(value = "/status", produces = "application/json") + public Map getStatus() throws SchedulerException { + log.trace("SCHEDULER - GET STATUS"); + String schedulerState = ""; + if (schedulerService.getScheduler().isShutdown() || !schedulerService.getScheduler().isStarted()) + schedulerState = SchedulerStates.STOPPED.toString(); + else if (schedulerService.getScheduler().isStarted() && schedulerService.getScheduler().isInStandbyMode()) + schedulerState = SchedulerStates.PAUSED.toString(); + else + schedulerState = SchedulerStates.RUNNING.toString(); + return Collections.singletonMap("data", schedulerState.toLowerCase()); + } + + @GetMapping("/pause") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void pause() throws SchedulerException { + log.info("SCHEDULER - PAUSE COMMAND"); + schedulerService.getScheduler().standby(); + } + + @GetMapping("/resume") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void resume() throws SchedulerException { + log.info("SCHEDULER - RESUME COMMAND"); + schedulerService.getScheduler().start(); + } + + @GetMapping("/run") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void run() throws SchedulerException { + log.info("SCHEDULER - START COMMAND"); + schedulerService.getScheduler().start(); + } + + @GetMapping("/stop") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void stop() throws SchedulerException { + log.info("SCHEDULER - STOP COMMAND"); + schedulerService.getScheduler().shutdown(true); + } } diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/controllers/TriggerController.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/controllers/TriggerController.java new file mode 100644 index 00000000..73307698 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/controllers/TriggerController.java @@ -0,0 +1,53 @@ +package it.fabioformosa.quartzmanager.controllers; + +import io.swagger.annotations.Api; +import it.fabioformosa.quartzmanager.dto.SchedulerConfigParam; +import it.fabioformosa.quartzmanager.dto.TriggerDTO; +import it.fabioformosa.quartzmanager.services.SchedulerService; +import lombok.extern.slf4j.Slf4j; +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@RequestMapping(TriggerController.TRIGGER_CONTROLLER_BASE_URL) +@RestController +@Api(value = "triggers") +public class TriggerController { + + static public final String TRIGGER_CONTROLLER_BASE_URL = "/quartz-manager/triggers"; + + @Value("${quartz-manager.jobClass}") + private String jobClassname; + + private SchedulerService schedulerService; + + public TriggerController(SchedulerService schedulerService) { + this.schedulerService = schedulerService; + } + + @GetMapping("/{name}") + public TriggerDTO getTrigger(@PathVariable String name) throws SchedulerException { + return schedulerService.getTriggerByName(name); + } + + @ResponseStatus(HttpStatus.CREATED) + @PostMapping("/{name}") + public TriggerDTO postTrigger(@PathVariable String name, @RequestBody SchedulerConfigParam config) throws SchedulerException, ClassNotFoundException { + log.info("TRIGGER - CREATING a trigger {} {}", name, config); + TriggerDTO newTriggerDTO = schedulerService.scheduleNewTrigger(name, jobClassname, config); + log.info("TRIGGER - CREATED a trigger {}", newTriggerDTO); + return newTriggerDTO; + } + + @PutMapping("/{name}") + public TriggerDTO rescheduleTrigger(@PathVariable String name, @RequestBody SchedulerConfigParam config) throws SchedulerException { + log.info("TRIGGER - RESCHEDULING the trigger {} {}", name, config); + TriggerDTO triggerDTO = schedulerService.rescheduleTrigger(name, config); + log.info("TRIGGER - RESCHEDULED the trigger {}", triggerDTO); + return triggerDTO; + } + + +} diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/controllers/UserController.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/controllers/UserController.java index 5e431ea3..9e1b8200 100644 --- a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/controllers/UserController.java +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/controllers/UserController.java @@ -12,7 +12,6 @@ @RequestMapping(value = "/quartz-manager/api", produces = MediaType.APPLICATION_JSON_VALUE) public class UserController { - @GetMapping("/whoami") public @ResponseBody Object user() { SecurityContext context = SecurityContextHolder.getContext(); @@ -21,12 +20,12 @@ public class UserController { return "\"NO_AUTH\""; } - /** - * JWT Temporary disabled - * - * @author Fabio.Formosa - * - */ +// /** +// * JWT Temporary disabled +// * +// * @author Fabio.Formosa +// * +// */ // @Autowired // private UserService userService; diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/converters/JobKeyToJobKeyDTO.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/converters/JobKeyToJobKeyDTO.java new file mode 100644 index 00000000..8bfd4855 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/converters/JobKeyToJobKeyDTO.java @@ -0,0 +1,15 @@ +package it.fabioformosa.quartzmanager.converters; + +import it.fabioformosa.metamorphosis.core.converters.AbstractBaseConverterToDTO; +import it.fabioformosa.quartzmanager.dto.JobKeyDTO; +import org.quartz.JobKey; +import org.springframework.stereotype.Component; + +@Component +public class JobKeyToJobKeyDTO extends AbstractBaseConverterToDTO { + @Override + protected void convert(JobKey source, JobKeyDTO target) { + target.setName(source.getName()); + target.setGroup(source.getGroup()); + } +} diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/converters/SchedulerToSchedulerDTO.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/converters/SchedulerToSchedulerDTO.java new file mode 100644 index 00000000..fcc01a7f --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/converters/SchedulerToSchedulerDTO.java @@ -0,0 +1,21 @@ +package it.fabioformosa.quartzmanager.converters; + +import it.fabioformosa.metamorphosis.core.converters.AbstractBaseConverterToDTO; +import it.fabioformosa.quartzmanager.dto.SchedulerDTO; +import lombok.SneakyThrows; +import org.quartz.Scheduler; +import org.quartz.impl.matchers.GroupMatcher; +import org.springframework.stereotype.Component; + +@Component +public class SchedulerToSchedulerDTO extends AbstractBaseConverterToDTO { + + @SneakyThrows + @Override + protected void convert(Scheduler source, SchedulerDTO target) { + target.setName(source.getSchedulerName()); + target.setInstanceId(source.getSchedulerInstanceId()); + target.setTriggerKeys(source.getTriggerKeys(GroupMatcher.anyTriggerGroup())); + } + +} diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/converters/TriggerKeyToTriggerKeyDTO.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/converters/TriggerKeyToTriggerKeyDTO.java new file mode 100644 index 00000000..ec5e01eb --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/converters/TriggerKeyToTriggerKeyDTO.java @@ -0,0 +1,16 @@ +package it.fabioformosa.quartzmanager.converters; + +import it.fabioformosa.metamorphosis.core.converters.AbstractBaseConverterToDTO; +import it.fabioformosa.quartzmanager.dto.TriggerKeyDTO; +import org.quartz.TriggerKey; +import org.springframework.stereotype.Component; + +@Component +public class TriggerKeyToTriggerKeyDTO extends AbstractBaseConverterToDTO { + + @Override + protected void convert(TriggerKey source, TriggerKeyDTO target) { + target.setName(source.getName()); + target.setGroup(source.getGroup()); + } +} diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/converters/TriggerToTriggerDTO.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/converters/TriggerToTriggerDTO.java new file mode 100644 index 00000000..479be884 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/converters/TriggerToTriggerDTO.java @@ -0,0 +1,36 @@ +package it.fabioformosa.quartzmanager.converters; + +import it.fabioformosa.metamorphosis.core.converters.AbstractBaseConverterToDTO; +import it.fabioformosa.quartzmanager.dto.JobKeyDTO; +import it.fabioformosa.quartzmanager.dto.TriggerDTO; +import it.fabioformosa.quartzmanager.dto.TriggerKeyDTO; +import org.quartz.JobKey; +import org.quartz.Trigger; +import org.quartz.TriggerKey; +import org.springframework.stereotype.Component; + +@Component +public class TriggerToTriggerDTO extends AbstractBaseConverterToDTO { + + @Override + protected void convert(Trigger source, TriggerDTO target) { + TriggerKey triggerKey = source.getKey(); + TriggerKeyDTO triggerKeyDTO = conversionService.convert(triggerKey, TriggerKeyDTO.class); + target.setTriggerKeyDTO(triggerKeyDTO); + + target.setStartTime(source.getStartTime()); + target.setDescription(source.getDescription()); + target.setEndTime(source.getEndTime()); + target.setFinalFireTime(source.getFinalFireTime()); + target.setMisfireInstruction(source.getMisfireInstruction()); + target.setNextFireTime(source.getNextFireTime()); + target.setPriority(source.getPriority()); + target.setMayFireAgain(source.mayFireAgain()); + + JobKey jobKey = source.getJobKey(); + JobKeyDTO jobKeyDTO = conversionService.convert(jobKey, JobKeyDTO.class); + target.setJobKeyDTO(jobKeyDTO); + + } + +} diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/dto/JobKeyDTO.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/dto/JobKeyDTO.java new file mode 100644 index 00000000..b8390b3c --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/dto/JobKeyDTO.java @@ -0,0 +1,15 @@ +package it.fabioformosa.quartzmanager.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Data +@Builder +public class JobKeyDTO { + private String name; + private String group; +} diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/dto/SchedulerConfigParam.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/dto/SchedulerConfigParam.java index 8caf0d13..f259a964 100644 --- a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/dto/SchedulerConfigParam.java +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/dto/SchedulerConfigParam.java @@ -1,40 +1,16 @@ package it.fabioformosa.quartzmanager.dto; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Data public class SchedulerConfigParam { - public long triggerPerDay; public int maxCount; - - public SchedulerConfigParam() { - super(); - } - - public SchedulerConfigParam(long triggerPerDay, int maxCount) { - super(); - this.triggerPerDay = triggerPerDay; - this.maxCount = maxCount; - } - - public int getMaxCount() { - return maxCount; - } - - public long getTriggerPerDay() { - return triggerPerDay; - } - - public void setMaxCount(int maxCount) { - this.maxCount = maxCount; - } - - public void setTriggerPerDay(long triggerPerDay) { - this.triggerPerDay = triggerPerDay; - } - - @Override - public String toString() { - return "SchedulerConfigParam [triggerPerDay=" + triggerPerDay - + ", maxCount=" + maxCount + "]"; - } - + public int timesTriggered; } diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/dto/SchedulerDTO.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/dto/SchedulerDTO.java new file mode 100644 index 00000000..7dca3478 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/dto/SchedulerDTO.java @@ -0,0 +1,35 @@ +package it.fabioformosa.quartzmanager.dto; + +import org.quartz.TriggerKey; + +import java.util.Set; + +public class SchedulerDTO { + private String name; + private String instanceId; + private Set triggerKeys; + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setInstanceId(String instanceId) { + this.instanceId = instanceId; + } + + public String getInstanceId() { + return instanceId; + } + + public void setTriggerKeys(Set triggerKeys) { + this.triggerKeys = triggerKeys; + } + + public Set getTriggerKeys() { + return triggerKeys; + } +} diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/dto/TriggerDTO.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/dto/TriggerDTO.java new file mode 100644 index 00000000..952c9b5a --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/dto/TriggerDTO.java @@ -0,0 +1,25 @@ +package it.fabioformosa.quartzmanager.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class TriggerDTO { + private TriggerKeyDTO triggerKeyDTO; + private int priority; + private Date startTime; + private String description; + private Date endTime; + private Date finalFireTime; + private int misfireInstruction; + private Date nextFireTime; + private JobKeyDTO jobKeyDTO; + private boolean mayFireAgain; +} diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/dto/TriggerKeyDTO.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/dto/TriggerKeyDTO.java new file mode 100644 index 00000000..4ef0f113 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/dto/TriggerKeyDTO.java @@ -0,0 +1,15 @@ +package it.fabioformosa.quartzmanager.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class TriggerKeyDTO { + private String name; + private String group; +} diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/services/SchedulerService.java b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/services/SchedulerService.java new file mode 100644 index 00000000..7900aee1 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-api/src/main/java/it/fabioformosa/quartzmanager/services/SchedulerService.java @@ -0,0 +1,114 @@ +package it.fabioformosa.quartzmanager.services; + +import it.fabioformosa.quartzmanager.common.utils.Try; +import it.fabioformosa.quartzmanager.dto.SchedulerConfigParam; +import it.fabioformosa.quartzmanager.dto.TriggerDTO; +import org.quartz.*; +import org.quartz.impl.matchers.GroupMatcher; +import org.springframework.core.convert.ConversionService; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +public class SchedulerService { + + public static final int MILLS_IN_A_DAY = 1000 * 60 * 60 * 24; + public static final int SEC_IN_A_DAY = 60 * 60 * 24; + + private Scheduler scheduler; + private ConversionService conversionService; + + public SchedulerService(Scheduler scheduler, ConversionService conversionService) { + this.scheduler = scheduler; + this.conversionService = conversionService; + } + + 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 + } + + public static int fromTriggerPerDayToSecInterval(long triggerPerDay) { + return (int) Math.ceil(Long.valueOf(SchedulerService.SEC_IN_A_DAY) / triggerPerDay); + } + + public static long fromMillsIntervalToTriggerPerDay(long repeatIntervalInMills) { + return (int) Math.ceil(MILLS_IN_A_DAY / repeatIntervalInMills); + } + + public Scheduler getScheduler() { + return scheduler; + } + + public Optional getTriggerByKey(String triggerKeyName) throws SchedulerException { + return scheduler.getTriggerKeys(GroupMatcher.anyGroup()).stream() + .filter(triggerKey -> triggerKey.getName().equals(triggerKeyName)) + .findFirst(); + } + + public Optional getOneSimpleTrigger() throws SchedulerException { + return getOneTriggerKey() + .map(Try.with(triggerKey -> scheduler.getTrigger(triggerKey))) + .filter(Try::isSuccess).map(Try::getSuccess) + .filter(trigger -> trigger instanceof SimpleTrigger) + .map(trigger -> (SimpleTrigger) trigger); + } + + public Optional getOneTriggerKey() throws SchedulerException { + return scheduler.getTriggerKeys(GroupMatcher.anyGroup()).stream() + .findFirst(); + } + + public TriggerDTO getTriggerByName(String name) throws SchedulerException { + Trigger trigger = scheduler.getTrigger(new TriggerKey(name)); + return conversionService.convert(trigger, TriggerDTO.class); + } + + public TriggerDTO scheduleNewTrigger(String name, String jobClassname, SchedulerConfigParam config) throws SchedulerException, ClassNotFoundException { + Class jobClass = (Class) Class.forName(jobClassname); + JobDetail jobDetail = JobBuilder.newJob() + .ofType(jobClass) + .storeDurably(false) + .build(); + + int intervalInMills = SchedulerService.fromTriggerPerDayToMillsInterval(config.getTriggerPerDay()); + + Trigger newTrigger = TriggerBuilder.newTrigger() + .withSchedule( + SimpleScheduleBuilder.simpleSchedule() + .withIntervalInMilliseconds(intervalInMills) + .withRepeatCount(config.getMaxCount() - 1) + .withMisfireHandlingInstructionNextWithRemainingCount() + ) + .withIdentity(name) + .build(); + + scheduler.scheduleJob(jobDetail, newTrigger); + + return conversionService.convert(newTrigger, TriggerDTO.class); + } + + public TriggerDTO rescheduleTrigger(String name, SchedulerConfigParam config) throws SchedulerException { + int intervalInMills = SchedulerService.fromTriggerPerDayToMillsInterval(config.getTriggerPerDay()); + + Optional optionalTriggerKey = getTriggerByKey(name); + TriggerKey triggerKey = optionalTriggerKey.orElse(TriggerKey.triggerKey(name)); + Trigger trigger = scheduler.getTrigger(triggerKey); + + Trigger newTrigger = TriggerBuilder.newTrigger() + .withSchedule( + SimpleScheduleBuilder.simpleSchedule() + .withIntervalInMilliseconds(intervalInMills) + .withRepeatCount(config.getMaxCount() - 1) + .withMisfireHandlingInstructionNextWithRemainingCount() + ) + .forJob(trigger.getJobKey().getName()) + .withIdentity(name) + .build(); + + scheduler.rescheduleJob(triggerKey, newTrigger); + + return conversionService.convert(newTrigger, TriggerDTO.class); + } + +} diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/QuartManagerApplicationTests.java b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/QuartManagerApplicationTests.java new file mode 100644 index 00000000..97f14ef8 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/QuartManagerApplicationTests.java @@ -0,0 +1,13 @@ +package it.fabioformosa.quartzmanager; + +import org.junit.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class QuartManagerApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/controllers/TriggerControllerTest.java b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/controllers/TriggerControllerTest.java new file mode 100644 index 00000000..aabdc42c --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/controllers/TriggerControllerTest.java @@ -0,0 +1,54 @@ +package it.fabioformosa.quartzmanager.controllers; + +import it.fabioformosa.quartzmanager.controllers.utils.TestUtils; +import it.fabioformosa.quartzmanager.controllers.utils.TriggerUtils; +import it.fabioformosa.quartzmanager.dto.SchedulerConfigParam; +import it.fabioformosa.quartzmanager.dto.TriggerDTO; +import it.fabioformosa.quartzmanager.services.SchedulerService; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +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.web.servlet.MockMvc; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + +import static org.mockito.ArgumentMatchers.any; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; + +@Disabled +@WebMvcTest(controllers = TriggerController.class, properties = { + "quartz-manager.jobClass=it.fabioformosa.quartzmanager.jobs.myjobs.SampleJob" +}) +class TriggerControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private SchedulerService schedulerService; + + @AfterEach + void cleanUp(){ + Mockito.reset(schedulerService); + } + + @Test + void givenASchedulerConfigParam_whenPosted_thenANewTriggerIsCreated() throws Exception { + TriggerDTO expectedTriggerDTO = TriggerUtils.getTriggerInstance(); + Mockito.when(schedulerService.scheduleNewTrigger(any(), any(), any())).thenReturn(expectedTriggerDTO); + + SchedulerConfigParam configParamToPost = SchedulerConfigParam.builder().maxCount(20).triggerPerDay(20000L).build(); + mockMvc.perform( + post(TriggerController.TRIGGER_CONTROLLER_BASE_URL + "mytrigger") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtils.toJson(configParamToPost)) + ) + .andExpect(MockMvcResultMatchers.status().isCreated()) + .andExpect(MockMvcResultMatchers.content().json(TestUtils.toJson(expectedTriggerDTO))) + ; + } +} diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/controllers/utils/TestUtils.java b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/controllers/utils/TestUtils.java new file mode 100644 index 00000000..34333cfa --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/controllers/utils/TestUtils.java @@ -0,0 +1,15 @@ +package it.fabioformosa.quartzmanager.controllers.utils; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.SneakyThrows; + +public class TestUtils { + + static public ObjectMapper objectMapper = new ObjectMapper(); + + @SneakyThrows + static public String toJson(Object object){ + return objectMapper.writeValueAsString(object); + }; + +} diff --git a/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/controllers/utils/TriggerUtils.java b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/controllers/utils/TriggerUtils.java new file mode 100644 index 00000000..6c04827c --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-api/src/test/java/it/fabioformosa/quartzmanager/controllers/utils/TriggerUtils.java @@ -0,0 +1,33 @@ +package it.fabioformosa.quartzmanager.controllers.utils; + +import it.fabioformosa.quartzmanager.common.utils.DateUtils; +import it.fabioformosa.quartzmanager.dto.JobKeyDTO; +import it.fabioformosa.quartzmanager.dto.TriggerDTO; +import it.fabioformosa.quartzmanager.dto.TriggerKeyDTO; + +import java.time.LocalDateTime; + +public class TriggerUtils { + + static public TriggerDTO getTriggerInstance(){ + return TriggerDTO.builder() + .description("sample trigger") + .endTime(DateUtils.getHoursFromNow(2L)) + .finalFireTime(DateUtils.getHoursFromNow(2L)) + .jobKeyDTO(JobKeyDTO.builder() + .group("defaultJobGroup") + .name("sampleJob") + .build()) + .mayFireAgain(true) + .triggerKeyDTO(TriggerKeyDTO.builder() + .group("defaultTriggerGroup") + .name("sampleTrigger") + .build()) + .misfireInstruction(1) + .nextFireTime(DateUtils.getHoursFromNow(1L)) + .priority(1) + .startTime(DateUtils.fromLocaleDateTimeToDate(LocalDateTime.now())) + .build(); + } + +} diff --git a/quartz-manager-parent/quartz-manager-starter-persistence/pom.xml b/quartz-manager-parent/quartz-manager-starter-persistence/pom.xml new file mode 100644 index 00000000..8f6908f7 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-persistence/pom.xml @@ -0,0 +1,51 @@ + + 4.0.0 + + it.fabioformosa.quartz-manager + quartz-manager-parent + 3.0.2-SNAPSHOT + + + quartz-manager-starter-persistence + + Quartz Manager Starter Security + Persist quartz jobs into a database + + https://github.com/fabioformosa/quartz-manager + + ${basedir}/../.. + UTF-8 + UTF-8 + 1.8 + + + + + it.fabioformosa.quartz-manager + quartz-manager-common + + + org.liquibase + liquibase-core + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.postgresql + postgresql + + + org.projectlombok + lombok + provided + + + + diff --git a/quartz-manager-parent/quartz-manager-starter-persistence/src/main/java/it/fabioformosa/quartzmanager/persistence/PersistenceConfig.java b/quartz-manager-parent/quartz-manager-starter-persistence/src/main/java/it/fabioformosa/quartzmanager/persistence/PersistenceConfig.java new file mode 100644 index 00000000..aa0621b4 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-persistence/src/main/java/it/fabioformosa/quartzmanager/persistence/PersistenceConfig.java @@ -0,0 +1,69 @@ +package it.fabioformosa.quartzmanager.persistence; + +import it.fabioformosa.quartzmanager.common.properties.QuartzModuleProperties; +import liquibase.integration.spring.SpringLiquibase; +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.context.annotation.*; + +import javax.sql.DataSource; + +@Configuration +@PropertySource("classpath:quartz-persistence.properties") +public class PersistenceConfig { + + @Value("${quartz-manager.persistence.quartz.datasource.url}") + private String quartzDatasourceUrl; + + @Value("${quartz-manager.persistence.quartz.datasource.user}") + private String quartzDatasourceUser; + + @Value("${quartz-manager.persistence.quartz.datasource.password}") + private String quartzDatasourcePassword; + + @Data + public class PersistenceDatasourceProps { + private String changeLog; + private String contexts; + } + + @Bean + public SpringLiquibase liquibase(PersistenceDatasourceProps persistenceDatasourceProps, DataSource quartzManagerDatasource) { + SpringLiquibase liquibase = new SpringLiquibase(); + liquibase.setContexts(persistenceDatasourceProps.getContexts()); + liquibase.setChangeLog(persistenceDatasourceProps.getChangeLog()); + liquibase.setDataSource(quartzManagerDatasource); + liquibase.setDropFirst(false); + return liquibase; + } + + @Bean + @ConfigurationProperties(prefix = "spring.liquibase") + public PersistenceDatasourceProps persistenceDatasourceProps() { + return new PersistenceDatasourceProps(); + } + + @Bean("quartzPersistenceProperties") + public QuartzModuleProperties persistenceQuartzProps(QuartzPersistencePropConfig quartzPersistencePropConfig) { + QuartzModuleProperties quartzModuleProperties = new QuartzModuleProperties(); + quartzModuleProperties.setProperties(quartzPersistencePropConfig.getProperties()); + quartzModuleProperties.getProperties().setProperty("org.quartz.dataSource.quartzDataSource.URL", quartzDatasourceUrl); + quartzModuleProperties.getProperties().setProperty("org.quartz.dataSource.quartzDataSource.user", quartzDatasourceUser); + quartzModuleProperties.getProperties().setProperty("org.quartz.dataSource.quartzDataSource.password", quartzDatasourcePassword); + return quartzModuleProperties; + } + + @Primary + @Bean + public DataSource quartzManagerDatasource(PersistenceDatasourceProps persistenceDatasourceProps) { + return DataSourceBuilder.create() + .url(quartzDatasourceUrl) + .driverClassName("org.postgresql.Driver") + .username(quartzDatasourceUser) + .password(quartzDatasourcePassword) + .build(); + } + +} diff --git a/quartz-manager-parent/quartz-manager-starter-persistence/src/main/java/it/fabioformosa/quartzmanager/persistence/QuartzPersistencePropConfig.java b/quartz-manager-parent/quartz-manager-starter-persistence/src/main/java/it/fabioformosa/quartzmanager/persistence/QuartzPersistencePropConfig.java new file mode 100644 index 00000000..800f95c4 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-persistence/src/main/java/it/fabioformosa/quartzmanager/persistence/QuartzPersistencePropConfig.java @@ -0,0 +1,17 @@ +package it.fabioformosa.quartzmanager.persistence; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +import java.util.Properties; + +@Configuration +@PropertySource("classpath:quartz-persistence.properties") +@ConfigurationProperties(prefix = "spring.quartz") +@Getter @Setter +public class QuartzPersistencePropConfig { + private Properties properties; +} diff --git a/quartz-manager-parent/quartz-manager-starter-persistence/src/main/resources/META-INF/spring.factories b/quartz-manager-parent/quartz-manager-starter-persistence/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..772bc91d --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-persistence/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +it.fabioformosa.quartzmanager.persistence.PersistenceConfig \ No newline at end of file diff --git a/quartz-manager-parent/quartz-manager-starter-persistence/src/main/resources/db/quartz-scheduler/liquibase-changelog-master.xml b/quartz-manager-parent/quartz-manager-starter-persistence/src/main/resources/db/quartz-scheduler/liquibase-changelog-master.xml new file mode 100644 index 00000000..ad7fc84b --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-persistence/src/main/resources/db/quartz-scheduler/liquibase-changelog-master.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/quartz-manager-parent/quartz-manager-starter-persistence/src/main/resources/db/quartz-scheduler/migrations/V202102190034_quartz_tables_postgresql.sql b/quartz-manager-parent/quartz-manager-starter-persistence/src/main/resources/db/quartz-scheduler/migrations/V202102190034_quartz_tables_postgresql.sql new file mode 100644 index 00000000..1fc97540 --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-persistence/src/main/resources/db/quartz-scheduler/migrations/V202102190034_quartz_tables_postgresql.sql @@ -0,0 +1,218 @@ +--liquibase formatted sql + +/* https://github.com/quartz-scheduler/quartz/blob/master/quartz-core/src/main/resources/org/quartz/impl/jdbcjobstore/tables_postgres.sql */ + +--changeset V202102190034_01 (dbms:postgresql) +DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; +DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; +DROP TABLE IF EXISTS QRTZ_LOCKS; +DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; +DROP TABLE IF EXISTS QRTZ_CALENDARS; + +--changeset V202102190034_02 (dbms:postgresql) +CREATE TABLE QRTZ_JOB_DETAILS +( + SCHED_NAME VARCHAR(120) NOT NULL, + JOB_NAME VARCHAR(200) NOT NULL, + JOB_GROUP VARCHAR(200) NOT NULL, + DESCRIPTION VARCHAR(250) NULL, + JOB_CLASS_NAME VARCHAR(250) NOT NULL, + IS_DURABLE BOOL NOT NULL, + IS_NONCONCURRENT BOOL NOT NULL, + IS_UPDATE_DATA BOOL NOT NULL, + REQUESTS_RECOVERY BOOL NOT NULL, + JOB_DATA BYTEA NULL, + PRIMARY KEY (SCHED_NAME, JOB_NAME, JOB_GROUP) +); + +--changeset V202102190034_03 (dbms:postgresql) +CREATE TABLE QRTZ_TRIGGERS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + JOB_NAME VARCHAR(200) NOT NULL, + JOB_GROUP VARCHAR(200) NOT NULL, + DESCRIPTION VARCHAR(250) NULL, + NEXT_FIRE_TIME BIGINT NULL, + PREV_FIRE_TIME BIGINT NULL, + PRIORITY INTEGER NULL, + TRIGGER_STATE VARCHAR(16) NOT NULL, + TRIGGER_TYPE VARCHAR(8) NOT NULL, + START_TIME BIGINT NOT NULL, + END_TIME BIGINT NULL, + CALENDAR_NAME VARCHAR(200) NULL, + MISFIRE_INSTR SMALLINT NULL, + JOB_DATA BYTEA NULL, + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME, JOB_NAME, JOB_GROUP) + REFERENCES QRTZ_JOB_DETAILS (SCHED_NAME, JOB_NAME, JOB_GROUP) +); + +--changeset V202102190034_04 (dbms:postgresql) +CREATE TABLE QRTZ_SIMPLE_TRIGGERS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + REPEAT_COUNT BIGINT NOT NULL, + REPEAT_INTERVAL BIGINT NOT NULL, + TIMES_TRIGGERED BIGINT NOT NULL, + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) +); + +--changeset V202102190034_05 (dbms:postgresql) +CREATE TABLE QRTZ_CRON_TRIGGERS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + CRON_EXPRESSION VARCHAR(120) NOT NULL, + TIME_ZONE_ID VARCHAR(80), + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) +); + +--changeset V202102190034_06 (dbms:postgresql) +CREATE TABLE QRTZ_SIMPROP_TRIGGERS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + STR_PROP_1 VARCHAR(512) NULL, + STR_PROP_2 VARCHAR(512) NULL, + STR_PROP_3 VARCHAR(512) NULL, + INT_PROP_1 INT NULL, + INT_PROP_2 INT NULL, + LONG_PROP_1 BIGINT NULL, + LONG_PROP_2 BIGINT NULL, + DEC_PROP_1 NUMERIC(13, 4) NULL, + DEC_PROP_2 NUMERIC(13, 4) NULL, + BOOL_PROP_1 BOOL NULL, + BOOL_PROP_2 BOOL NULL, + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) +); + +--changeset V202102190034_07 (dbms:postgresql) +CREATE TABLE QRTZ_BLOB_TRIGGERS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + BLOB_DATA BYTEA NULL, + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) +); + +--changeset V202102190034_08 (dbms:postgresql) +CREATE TABLE QRTZ_CALENDARS +( + SCHED_NAME VARCHAR(120) NOT NULL, + CALENDAR_NAME VARCHAR(200) NOT NULL, + CALENDAR BYTEA NOT NULL, + PRIMARY KEY (SCHED_NAME, CALENDAR_NAME) +); + +--changeset V202102190034_09 (dbms:postgresql) +CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + PRIMARY KEY (SCHED_NAME, TRIGGER_GROUP) +); + +--changeset V202102190034_10 (dbms:postgresql) +CREATE TABLE QRTZ_FIRED_TRIGGERS +( + SCHED_NAME VARCHAR(120) NOT NULL, + ENTRY_ID VARCHAR(95) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + INSTANCE_NAME VARCHAR(200) NOT NULL, + FIRED_TIME BIGINT NOT NULL, + SCHED_TIME BIGINT NOT NULL, + PRIORITY INTEGER NOT NULL, + STATE VARCHAR(16) NOT NULL, + JOB_NAME VARCHAR(200) NULL, + JOB_GROUP VARCHAR(200) NULL, + IS_NONCONCURRENT BOOL NULL, + REQUESTS_RECOVERY BOOL NULL, + PRIMARY KEY (SCHED_NAME, ENTRY_ID) +); + +--changeset V202102190034_11 (dbms:postgresql) +CREATE TABLE QRTZ_SCHEDULER_STATE +( + SCHED_NAME VARCHAR(120) NOT NULL, + INSTANCE_NAME VARCHAR(200) NOT NULL, + LAST_CHECKIN_TIME BIGINT NOT NULL, + CHECKIN_INTERVAL BIGINT NOT NULL, + PRIMARY KEY (SCHED_NAME, INSTANCE_NAME) +); + +--changeset V202102190034_12 (dbms:postgresql) +CREATE TABLE QRTZ_LOCKS +( + SCHED_NAME VARCHAR(120) NOT NULL, + LOCK_NAME VARCHAR(40) NOT NULL, + PRIMARY KEY (SCHED_NAME, LOCK_NAME) +); + +--changeset V202102190034_13 (dbms:postgresql) +CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY + ON QRTZ_JOB_DETAILS (SCHED_NAME, REQUESTS_RECOVERY); +CREATE INDEX IDX_QRTZ_J_GRP + ON QRTZ_JOB_DETAILS (SCHED_NAME, JOB_GROUP); + +CREATE INDEX IDX_QRTZ_T_J + ON QRTZ_TRIGGERS (SCHED_NAME, JOB_NAME, JOB_GROUP); +CREATE INDEX IDX_QRTZ_T_JG + ON QRTZ_TRIGGERS (SCHED_NAME, JOB_GROUP); +CREATE INDEX IDX_QRTZ_T_C + ON QRTZ_TRIGGERS (SCHED_NAME, CALENDAR_NAME); +CREATE INDEX IDX_QRTZ_T_G + ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_GROUP); +CREATE INDEX IDX_QRTZ_T_STATE + ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_N_STATE + ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP, TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_N_G_STATE + ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_GROUP, TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME + ON QRTZ_TRIGGERS (SCHED_NAME, NEXT_FIRE_TIME); +CREATE INDEX IDX_QRTZ_T_NFT_ST + ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_STATE, NEXT_FIRE_TIME); +CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE + ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME); +CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE + ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP + ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_GROUP, TRIGGER_STATE); + +CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, INSTANCE_NAME); +CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, INSTANCE_NAME, REQUESTS_RECOVERY); +CREATE INDEX IDX_QRTZ_FT_J_G + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, JOB_NAME, JOB_GROUP); +CREATE INDEX IDX_QRTZ_FT_JG + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, JOB_GROUP); +CREATE INDEX IDX_QRTZ_FT_T_G + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP); +CREATE INDEX IDX_QRTZ_FT_TG + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, TRIGGER_GROUP); + + +COMMIT; diff --git a/quartz-manager-parent/quartz-manager-starter-persistence/src/main/resources/quartz-persistence.properties b/quartz-manager-parent/quartz-manager-starter-persistence/src/main/resources/quartz-persistence.properties new file mode 100644 index 00000000..fbd821ce --- /dev/null +++ b/quartz-manager-parent/quartz-manager-starter-persistence/src/main/resources/quartz-persistence.properties @@ -0,0 +1,13 @@ +spring.liquibase.change-log=classpath:db/quartz-scheduler/liquibase-changelog-master.xml +spring.liquibase.contexts=default +spring.quartz.job-store-type=jdbc +spring.quartz.initialize-schema=never +spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate +spring.quartz.properties.org.quartz.dataSource.quartzDataSource.driver=org.postgresql.Driver +spring.quartz.properties.org.quartz.dataSource.quartzDataSource.maxConnections=5 +spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX +spring.quartz.properties.org.quartz.jobStore.dataSource=quartzDataSource +spring.quartz.properties.org.quartz.dataSource.quartzDataSource.provider=hikaricp +spring.quartz.properties.org.quartz.jobStore.misfireThreshold=1000 +# org.quartz.jobStore.isClustered=true +# org.quartz.scheduler.instanceId=AUTO diff --git a/quartz-manager-parent/quartz-manager-web-showcase/pom.xml b/quartz-manager-parent/quartz-manager-web-showcase/pom.xml index c3477584..73755dd5 100644 --- a/quartz-manager-parent/quartz-manager-web-showcase/pom.xml +++ b/quartz-manager-parent/quartz-manager-web-showcase/pom.xml @@ -1,144 +1,151 @@ - - - 4.0.0 - - - it.fabioformosa.quartz-manager - quartz-manager-parent - 3.0.2-SNAPSHOT - - - quartz-manager-web-showcase - - war - - Quartz Manager Web Showcase - A webapp that imports Quartz Manager API lib and the frontend webjar - - - UTF-8 - UTF-8 - 2.9.2 - 1.8 - - - - - it.fabioformosa.quartz-manager - quartz-manager-starter-api - - - it.fabioformosa.quartz-manager - quartz-manager-starter-ui - - - it.fabioformosa.quartz-manager - quartz-manager-starter-security - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-security - - - org.springframework.boot - spring-boot-starter-data-jpa - - - org.springframework.boot - spring-boot-devtools - - - org.springframework.boot - spring-boot-configuration-processor - true - - - org.springframework.boot - spring-boot-starter-tomcat - provided - - - org.springframework.boot - spring-boot-starter-test - test - - - - - io.jsonwebtoken - jjwt - 0.9.0 - - - com.fasterxml.jackson.core - jackson-databind - - - com.fasterxml.jackson.core - jackson-annotations - - - com.h2database - h2 - runtime - - - org.codehaus.groovy - groovy - - - net.sourceforge.nekohtml - nekohtml - - - io.rest-assured - spring-mock-mvc - test - - - org.projectlombok - lombok - provided - - - org.apache.commons - commons-lang3 - - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.0 - - 1.8 - 1.8 - - - - - - - - + + + 4.0.0 + + + it.fabioformosa.quartz-manager + quartz-manager-parent + 3.0.2-SNAPSHOT + + + quartz-manager-web-showcase + + war + + Quartz Manager Web Showcase + A webapp that imports Quartz Manager API lib and the frontend webjar + + + UTF-8 + UTF-8 + 2.9.2 + 1.8 + + + + + it.fabioformosa.quartz-manager + quartz-manager-starter-api + + + it.fabioformosa.quartz-manager + quartz-manager-starter-ui + + + it.fabioformosa.quartz-manager + quartz-manager-starter-security + + + it.fabioformosa.quartz-manager + quartz-manager-starter-persistence + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-devtools + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-tomcat + provided + + + org.springframework.boot + spring-boot-starter-test + test + + + + + io.jsonwebtoken + jjwt + 0.9.0 + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.core + jackson-annotations + + + com.h2database + h2 + runtime + + + org.codehaus.groovy + groovy + + + net.sourceforge.nekohtml + nekohtml + + + io.rest-assured + spring-mock-mvc + test + + + org.projectlombok + lombok + provided + + + org.apache.commons + commons-lang3 + + + + + org.junit.platform + junit-platform-launcher + test + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 1.8 + 1.8 + + + + + + + + diff --git a/quartz-manager-parent/quartz-manager-web-showcase/src/main/java/it/fabioformosa/quartzmanager/jobs/myjobs/SampleJob.java b/quartz-manager-parent/quartz-manager-web-showcase/src/main/java/it/fabioformosa/quartzmanager/jobs/myjobs/SampleJob.java index 203c5b5d..7c139815 100644 --- a/quartz-manager-parent/quartz-manager-web-showcase/src/main/java/it/fabioformosa/quartzmanager/jobs/myjobs/SampleJob.java +++ b/quartz-manager-parent/quartz-manager-web-showcase/src/main/java/it/fabioformosa/quartzmanager/jobs/myjobs/SampleJob.java @@ -8,10 +8,9 @@ public class SampleJob extends AbstractLoggingJob { - @Override public LogRecord doIt(JobExecutionContext jobExecutionContext) { - return new LogRecord(LogType.INFO, "Hello!"); + return new LogRecord(LogType.INFO, "Hello!"); } } diff --git a/quartz-manager-parent/quartz-manager-web-showcase/src/main/resources/application.yml b/quartz-manager-parent/quartz-manager-web-showcase/src/main/resources/application.yml index c2115fa6..0b9e144f 100644 --- a/quartz-manager-parent/quartz-manager-web-showcase/src/main/resources/application.yml +++ b/quartz-manager-parent/quartz-manager-web-showcase/src/main/resources/application.yml @@ -26,8 +26,15 @@ logging: org.springframework.security: INFO org.springframework.boot.autoconfigure.security: INFO it.fabioformosa: DEBUG + org.quartz: INFO quartz-manager: + persistence: + quartz: + datasource: + url: "jdbc:postgresql://localhost:5432/quartzmanager" + user: "quartzmanager" + password: "quartzmanager" security: login-model: form-login-enabled: true diff --git a/quartz-manager-parent/quartz-manager-web-showcase/src/main/resources/logback.xml b/quartz-manager-parent/quartz-manager-web-showcase/src/main/resources/logback.xml index 90577b11..96206fc1 100644 --- a/quartz-manager-parent/quartz-manager-web-showcase/src/main/resources/logback.xml +++ b/quartz-manager-parent/quartz-manager-web-showcase/src/main/resources/logback.xml @@ -12,6 +12,9 @@ + + + @@ -19,8 +22,8 @@ - + - \ No newline at end of file + diff --git a/quartz-manager-parent/quartz-manager-web-showcase/src/main/resources/quartz.properties b/quartz-manager-parent/quartz-manager-web-showcase/src/main/resources/quartz.properties index bf7cc50d..cdcc9e43 100644 --- a/quartz-manager-parent/quartz-manager-web-showcase/src/main/resources/quartz.properties +++ b/quartz-manager-parent/quartz-manager-web-showcase/src/main/resources/quartz.properties @@ -1,3 +1,2 @@ org.quartz.scheduler.instanceName=example -org.quartz.scheduler.instanceId=AUTO -org.quartz.threadPool.threadCount=1 \ No newline at end of file +org.quartz.threadPool.threadCount=1