diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs
new file mode 100644
index 0000000..f897a7f
--- /dev/null
+++ b/.settings/org.eclipse.m2e.core.prefs
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/src/main/asciidoc/endpoints/quartz.adoc b/src/main/asciidoc/endpoints/quartz.adoc
new file mode 100644
index 0000000..b1c1f68
--- /dev/null
+++ b/src/main/asciidoc/endpoints/quartz.adoc
@@ -0,0 +1,113 @@
+[[quartz]]
+= Quartz
+
+Quartz actuator provides end points for managing jobs and triggers
+
+[[quartz-jobs]]
+== Quartz Job (`quartz-jobs`)
+The `quartz-jobs` endpoint provides access to application's quartz jobs
+
+[[list-job]]
+=== Retrieving jobs
+To retrieve jobs, make `GET` request to `/actuator/quartz-jobs` as shown in the following curl-based example:
+
+include::{snippets}/quartz-jobs/list/curl-request.adoc[]
+
+The resulting response is similar to the following:
+
+include::{snippets}/quartz-jobs/list/http-response.adoc[]
+
+[[list-job-query-parameter]]
+==== Query parameter
+Jobs listing can be further filtered using `group` and `name` query parameter.The following table shows the supported query parameters:
+
+[cols="2,4"]
+include::{snippets}/quartz-jobs/list/request-parameters.adoc[]
+
+[[list-job-response-structure]]
+==== Response Structure
+The response contains the details of the jobs managed by quartz.The following table describes
+the structure of the response:
+
+[cols="3,1,3"]
+include::{snippets}/quartz-jobs/list/response-fields.adoc[]
+
+[[job-detail]]
+=== Retrieving Job details
+To retrieve job details with trigger information, make GET request to `/actuator/quartz-jobs/{group}/{name}` as shown in the following curl-based example:
+
+include::{snippets}/quartz-jobs/named/jobdetails/curl-request.adoc[]
+
+[[job-detail-response-structure]]
+==== Response Structure
+The response contains details about the job and its associated triggers. The following table describes the structure of the response:
+
+[cols="3,1,3"]
+include::{snippets}/quartz-jobs/named/jobdetails/response-fields.adoc[]
+
+[[pause-job]]
+=== Pause Job
+To pause particular job, make a `POST` request to `/actuator/quartz-jobs/{group}/{name}/pause` as shown in the following curl-based example:
+
+include::{snippets}/quartz-jobs/named/job/pause/curl-request.adoc[]
+
+
+[[resume-job]]
+=== Resume Job
+To resume particular job, make a `POST` request to `/actuator/quartz-jobs/{group}/{name}/resume` as shown in the following curl-based example:
+
+include::{snippets}/quartz-jobs/named/job/resume/curl-request.adoc[]
+
+[[pause-jobgroup]]
+=== Pause Job group
+To pause particular job group, make `POST` request to `/actuator/quartz-jobs/{group}` as shown in the following curl-based example:
+
+include::{snippets}/quartz-jobs/named/jobgroup/pause/curl-request.adoc[]
+
+[[resume-jobgroup]]
+=== Resume Job group
+To resume particular job group, make `POST` request to `/actuator/quartz-jobs/{group}` as shown in the following curl-based example:
+
+include::{snippets}/quartz-jobs/named/jobgroup/resume/curl-request.adoc[]
+
+[[quartz-triggers]]
+== Quartz Triggers (`quartz-triggers`)
+The `quartz-triggers` endpoint provides access to application's quartz triggers
+
+[[list-triggers]]
+=== Retrieving Triggers
+To retrieve triggers, make `GET` request to `/actuator/quartz-triggers` as shown in the following curl-based example:
+
+include::{snippets}/quartz-jobs/list/curl-request.adoc[]
+
+The resulting response is similar to the following:
+
+include::{snippets}/quartz-triggers/list/http-response.adoc[]
+
+[[list-trigger-query-parameter]]
+==== Query parameter
+Triggers listing can be further filtered using `group` and `name` query parameter.The following table shows the supported query parameters:
+
+[cols="2,4"]
+include::{snippets}/quartz-triggers/list/request-parameters.adoc[]
+
+[[list-trigger-response-structure]]
+==== Response Structure
+The response contains the details of the triggers managed by quartz.The following table describes
+the structure of the response:
+
+[cols="3,1,3"]
+include::{snippets}/quartz-triggers/list/response-fields.adoc[]
+
+[[pause-trigger]]
+=== Pause Trigger
+To pause particular trigger, make a `POST` request to `/actuator/quartz-triggers/{group}/{name}/pause` as shown in the following curl-based example:
+
+include::{snippets}/quartz-triggers/named/trigger/pause/curl-request.adoc[]
+
+
+[[resume-trigger]]
+=== Resume Trigger
+To resume particular trigger, make a `POST` request to `/actuator/quartz-triggers/{group}/{name}/resume` as shown in the following curl-based example:
+
+include::{snippets}/quartz-triggers/named/trigger/resume/curl-request.adoc[]
diff --git a/src/main/asciidoc/endpoints/quartz.adoc.md b/src/main/asciidoc/endpoints/quartz.adoc.md
new file mode 100644
index 0000000..2d83705
--- /dev/null
+++ b/src/main/asciidoc/endpoints/quartz.adoc.md
@@ -0,0 +1,25 @@
+[[quartz]] = Quartz
+
+Quartz actuator provides end points for managing jobs and triggers
+
+[[quartz-jobs]] == Quartz Job (`quartz-jobs`)
+
+The `quartz-jobs` endpoint provides access to application's quartz jobs
+
+[[pause-job]] === Pause Job To pause particular job, make a `POST`
+request to `/actuator/quartz-jobs/{group}/{name}/pause` as shown in the
+following curl-based example:
+
+include::{snippets}/quartz-jobs/named/job/pause/curl-request.adoc[]
+
+[[resume-job]] === Resume Job To resume particular job, make a `POST`
+request to `/actuator/quartz-jobs/{group}/{name}/resume` as shown in the
+following curl-based example:
+
+include::{snippets}/quartz-jobs/named/job/resume/curl-request.adoc[]
+
+[[pause-jobgroup]] === Pause Job group To pause particular job group,
+make `POST` request to `/actuator/quartz-jobs/{group}` as shown in the
+following curl-based example:
+
+include::{snippets}/quartz-jobs/named/jobgroup/pause/curl-request.adoc[]
diff --git a/src/main/asciidoc/endpoints/quartz.adoc.xml b/src/main/asciidoc/endpoints/quartz.adoc.xml
new file mode 100644
index 0000000..80ba591
--- /dev/null
+++ b/src/main/asciidoc/endpoints/quartz.adoc.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+ [[quartz]] = Quartz
+
+
+ Quartz actuator provides end points for managing jobs and triggers
+
+
+ [[quartz-jobs]] == Quartz Job (quartz-jobs)
+
+
+ The quartz-jobs endpoint provides access to
+ application's quartz jobs
+
+
+ [[pause-job]] === Pause Job To pause particular job, make a
+ POST request to
+ /actuator/quartz-jobs/{group}/{name}/pause as shown
+ in the following curl-based example:
+
+
+ include::{snippets}/quartz-jobs/named/job/pause/curl-request.adoc[]
+
+
+ [[resume-job]] === Resume Job To resume particular job, make a
+ POST request to
+ /actuator/quartz-jobs/{group}/{name}/resume as
+ shown in the following curl-based example:
+
+
+ include::{snippets}/quartz-jobs/named/job/resume/curl-request.adoc[]
+
+
+ [[pause-jobgroup]] === Pause Job group To pause particular job group,
+ make POST request to
+ /actuator/quartz-jobs/{group} as shown in the
+ following curl-based example:
+
+
+ include::{snippets}/quartz-jobs/named/jobgroup/pause/curl-request.adoc[]
+
+
diff --git a/src/main/asciidoc/index.adoc b/src/main/asciidoc/index.adoc
new file mode 100644
index 0000000..94841f4
--- /dev/null
+++ b/src/main/asciidoc/index.adoc
@@ -0,0 +1,10 @@
+:doctype: book
+:toc: left
+:toclevels: 4
+:source-highlighter: prettify
+:numbered:
+:icons: font
+:hide-uri-scheme:
+:docinfo: shared,private
+
+include::endpoints/quartz.adoc[leveloffset=+1]
diff --git a/src/main/java/org/sathyabodh/actuator/autoconfigure/cache/CachesEndPointAutoConfiguration.java b/src/main/java/org/sathyabodh/actuator/autoconfigure/cache/CachesEndPointAutoConfiguration.java
new file mode 100644
index 0000000..29d8314
--- /dev/null
+++ b/src/main/java/org/sathyabodh/actuator/autoconfigure/cache/CachesEndPointAutoConfiguration.java
@@ -0,0 +1,34 @@
+package org.sathyabodh.actuator.autoconfigure.cache;
+
+import java.util.Map;
+
+import org.sathyabodh.actuator.cache.CachesEndPoint;
+import org.sathyabodh.actuator.cache.CachesEndPointWebExtension;
+import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.cache.CacheManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConditionalOnClass(CacheManager.class)
+@AutoConfigureAfter(CacheAutoConfiguration.class)
+public class CachesEndPointAutoConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ @ConditionalOnEnabledEndpoint
+ public CachesEndPoint cachesEndPoint(Map cacheManagers){
+ return new CachesEndPoint(cacheManagers);
+ }
+
+ @Bean
+ @ConditionalOnBean(CachesEndPoint.class)
+ public CachesEndPointWebExtension cachesEndPointWebExtension(CachesEndPoint cachesEndPoint){
+ return new CachesEndPointWebExtension(cachesEndPoint);
+ }
+}
diff --git a/src/main/java/org/sathyabodh/actuator/autoconfigure/quartz/QuartzTriggerEndPointAutoConfiguration.java b/src/main/java/org/sathyabodh/actuator/autoconfigure/quartz/QuartzTriggerEndPointAutoConfiguration.java
new file mode 100644
index 0000000..442dc9c
--- /dev/null
+++ b/src/main/java/org/sathyabodh/actuator/autoconfigure/quartz/QuartzTriggerEndPointAutoConfiguration.java
@@ -0,0 +1,33 @@
+package org.sathyabodh.actuator.autoconfigure.quartz;
+
+import org.quartz.Scheduler;
+import org.quartz.SchedulerFactory;
+import org.sathyabodh.actuator.quartz.QuartzTriggerEndPoint;
+import org.sathyabodh.actuator.quartz.QuartzTriggerEndPointWebExtension;
+import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConditionalOnClass({Scheduler.class, SchedulerFactory.class})
+@AutoConfigureAfter(QuartzAutoConfiguration.class)
+public class QuartzTriggerEndPointAutoConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ @ConditionalOnEnabledEndpoint
+ public QuartzTriggerEndPoint quartzTriggerEndPoint(Scheduler scheduler){
+ return new QuartzTriggerEndPoint(scheduler);
+ }
+
+ @Bean
+ @ConditionalOnBean(QuartzTriggerEndPoint.class)
+ public QuartzTriggerEndPointWebExtension quartzTriggerEndPointWebExtension(QuartzTriggerEndPoint quartzTriggerEndPoint){
+ return new QuartzTriggerEndPointWebExtension(quartzTriggerEndPoint);
+ }
+}
diff --git a/src/main/java/org/sathyabodh/actuator/autoconfigure/quartz/QurtzJobEndPointAutoConfiguration.java b/src/main/java/org/sathyabodh/actuator/autoconfigure/quartz/QurtzJobEndPointAutoConfiguration.java
new file mode 100644
index 0000000..be8f24d
--- /dev/null
+++ b/src/main/java/org/sathyabodh/actuator/autoconfigure/quartz/QurtzJobEndPointAutoConfiguration.java
@@ -0,0 +1,34 @@
+package org.sathyabodh.actuator.autoconfigure.quartz;
+
+import org.quartz.Scheduler;
+import org.quartz.SchedulerFactory;
+import org.sathyabodh.actuator.quartz.QuartzJobEndPoint;
+import org.sathyabodh.actuator.quartz.QuartzJobEndPointWebExtension;
+import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConditionalOnClass({ Scheduler.class, SchedulerFactory.class })
+@AutoConfigureAfter(QuartzAutoConfiguration.class)
+public class QurtzJobEndPointAutoConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ @ConditionalOnEnabledEndpoint
+ public QuartzJobEndPoint quartzJobEndPoint(Scheduler scheduler) {
+ return new QuartzJobEndPoint(scheduler);
+ }
+
+ @Bean
+ @ConditionalOnBean(QuartzJobEndPoint.class)
+ public QuartzJobEndPointWebExtension quartzJobEndPointWebExtension(QuartzJobEndPoint quartzJobEndPoint) {
+ return new QuartzJobEndPointWebExtension(quartzJobEndPoint);
+ }
+
+}
diff --git a/src/main/java/org/sathyabodh/actuator/cache/CachesEndPoint.java b/src/main/java/org/sathyabodh/actuator/cache/CachesEndPoint.java
new file mode 100644
index 0000000..707e71e
--- /dev/null
+++ b/src/main/java/org/sathyabodh/actuator/cache/CachesEndPoint.java
@@ -0,0 +1,77 @@
+package org.sathyabodh.actuator.cache;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.sathyabodh.actuator.cache.exception.NonUniqueCacheException;
+import org.sathyabodh.actuator.cache.model.CacheDetailModel;
+import org.sathyabodh.actuator.cache.model.CacheEntryModel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation;
+import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
+import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
+import org.springframework.boot.actuate.endpoint.annotation.Selector;
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.lang.Nullable;
+
+@Endpoint(id="caches")
+public class CachesEndPoint {
+
+ private static Logger log = LoggerFactory.getLogger(CachesEndPoint.class);
+ private Map cacheManagers ;
+
+ public CachesEndPoint(Map cacheManagers) {
+ this.cacheManagers = cacheManagers;
+ }
+
+ @ReadOperation
+ public CacheDetailModel caches(){
+ final CacheDetailModel detailModel = new CacheDetailModel();
+ cacheManagers.entrySet().forEach(entry->detailModel.get(entry.getKey()).add(entry.getValue()));
+ return detailModel;
+ }
+
+ @ReadOperation
+ public CacheEntryModel cache(@Selector String name, @Nullable String cacheManager){
+ return findUniqueCache(name, cacheManager);
+ }
+
+ @DeleteOperation
+ public boolean evictCaches(@Selector String name, @Nullable String cacheManager){
+ CacheEntryModel cache = findUniqueCache(name, cacheManager);
+ return cache == null ? false:clearCache(cache);
+ }
+
+ private boolean clearCache(CacheEntryModel cacheEntryModel){
+ CacheManager cacheManager = cacheManagers.get(cacheEntryModel.getCacheManager());
+ if(cacheManager == null){
+ return false;
+ }
+ Cache cache = cacheManager.getCache(cacheEntryModel.getName());
+ if(cache == null){
+ return false;
+ }
+
+ log.info("Clearning cache:[{}]", cache.getName());
+ cache.clear();
+ log.info("Cleared cache:[{}]", cache.getName());
+ return true;
+ }
+
+ private CacheEntryModel findUniqueCache(String name, String cacheManager){
+ List caches = cacheManagers.entrySet().stream().filter(it-> cacheManager == null || cacheManager.equals(it.getKey()))
+ .map(entry->entry.getValue().getCache(name) == null ? null : new CacheEntryModel(entry.getKey(), entry.getValue().getCache(name)))
+ .filter(entry->entry != null).collect(Collectors.toList());
+ if(caches.size() > 1){
+ throw new NonUniqueCacheException(
+ String.format("Non unique cache name:%s. "
+ + "Please use cacheManager name to uniquely identify", name));
+ }
+ return caches.isEmpty() ? null : caches.get(0);
+ }
+
+
+}
diff --git a/src/main/java/org/sathyabodh/actuator/cache/CachesEndPointWebExtension.java b/src/main/java/org/sathyabodh/actuator/cache/CachesEndPointWebExtension.java
new file mode 100644
index 0000000..eda0f1d
--- /dev/null
+++ b/src/main/java/org/sathyabodh/actuator/cache/CachesEndPointWebExtension.java
@@ -0,0 +1,51 @@
+package org.sathyabodh.actuator.cache;
+
+import org.sathyabodh.actuator.cache.exception.NonUniqueCacheException;
+import org.sathyabodh.actuator.cache.model.CacheEntryModel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation;
+import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
+import org.springframework.boot.actuate.endpoint.annotation.Selector;
+import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
+import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
+import org.springframework.lang.Nullable;
+
+
+@EndpointWebExtension(endpoint = CachesEndPoint.class)
+public class CachesEndPointWebExtension {
+ private static Logger log = LoggerFactory.getLogger(CachesEndPoint.class);
+
+ private CachesEndPoint cachesEndPoint;
+
+ public CachesEndPointWebExtension(CachesEndPoint cachesEndPoint) {
+ this.cachesEndPoint = cachesEndPoint;
+ }
+
+ @ReadOperation
+ public WebEndpointResponse cache(@Selector String name, @Nullable String cacheManager) {
+ CacheEntryModel model = null;
+ int status;
+ try {
+ model = cachesEndPoint.cache(name, cacheManager);
+ status = null == model ? WebEndpointResponse.STATUS_NOT_FOUND : WebEndpointResponse.STATUS_OK;
+ } catch (NonUniqueCacheException e) {
+ log.error("Error in getting cache detials", e);
+ status = WebEndpointResponse.STATUS_BAD_REQUEST;
+ }
+ return new WebEndpointResponse<>(model, status);
+ }
+
+ @DeleteOperation
+ public WebEndpointResponse delete(@Selector String name, @Nullable String cacheManager) {
+ int status;
+ try {
+ boolean isSuccess = cachesEndPoint.evictCaches(name, cacheManager);
+ status = isSuccess ? WebEndpointResponse.STATUS_OK : WebEndpointResponse.STATUS_NOT_FOUND;
+ } catch (NonUniqueCacheException e) {
+ log.error("Error in clearing cache detials", e);
+ status = WebEndpointResponse.STATUS_BAD_REQUEST;
+ }
+ return new WebEndpointResponse<>(status);
+ }
+}
diff --git a/src/main/java/org/sathyabodh/actuator/cache/exception/NonUniqueCacheException.java b/src/main/java/org/sathyabodh/actuator/cache/exception/NonUniqueCacheException.java
new file mode 100644
index 0000000..58115cd
--- /dev/null
+++ b/src/main/java/org/sathyabodh/actuator/cache/exception/NonUniqueCacheException.java
@@ -0,0 +1,13 @@
+package org.sathyabodh.actuator.cache.exception;
+
+public class NonUniqueCacheException extends RuntimeException {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+ public NonUniqueCacheException(String message){
+ super(message);
+ }
+
+}
diff --git a/src/main/java/org/sathyabodh/actuator/cache/model/CacheDetailModel.java b/src/main/java/org/sathyabodh/actuator/cache/model/CacheDetailModel.java
new file mode 100644
index 0000000..2324e7c
--- /dev/null
+++ b/src/main/java/org/sathyabodh/actuator/cache/model/CacheDetailModel.java
@@ -0,0 +1,22 @@
+package org.sathyabodh.actuator.cache.model;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class CacheDetailModel {
+
+ private Map cacheManagers = new HashMap<>();
+
+ public CacheManagerModel get(String cacheManager){
+ CacheManagerModel model = getCacheManagers().get(cacheManager);
+ if(model == null){
+ model = new CacheManagerModel();
+ getCacheManagers().put(cacheManager, model);
+ }
+ return model;
+ }
+
+ public Map getCacheManagers() {
+ return cacheManagers;
+ }
+}
diff --git a/src/main/java/org/sathyabodh/actuator/cache/model/CacheEntryModel.java b/src/main/java/org/sathyabodh/actuator/cache/model/CacheEntryModel.java
new file mode 100644
index 0000000..c255dcf
--- /dev/null
+++ b/src/main/java/org/sathyabodh/actuator/cache/model/CacheEntryModel.java
@@ -0,0 +1,26 @@
+package org.sathyabodh.actuator.cache.model;
+
+import org.springframework.cache.Cache;
+
+public class CacheEntryModel extends CacheModel {
+ private String name;
+ private String cacheManager;
+
+ public CacheEntryModel(String cacheManager, Cache cache){
+ super(cache.getNativeCache().getClass().getName());
+ this.setName(cache.getName());
+ this.setCacheManager(cacheManager);
+ }
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+ public String getCacheManager() {
+ return cacheManager;
+ }
+ public void setCacheManager(String cacheManager) {
+ this.cacheManager = cacheManager;
+ }
+}
diff --git a/src/main/java/org/sathyabodh/actuator/cache/model/CacheManagerModel.java b/src/main/java/org/sathyabodh/actuator/cache/model/CacheManagerModel.java
new file mode 100644
index 0000000..8990fe2
--- /dev/null
+++ b/src/main/java/org/sathyabodh/actuator/cache/model/CacheManagerModel.java
@@ -0,0 +1,24 @@
+package org.sathyabodh.actuator.cache.model;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+
+public class CacheManagerModel {
+ private Map caches = new HashMap<>();
+
+ public void add(CacheManager cacheManager){
+ cacheManager.getCacheNames().forEach(name->addEntry(cacheManager.getCache(name)));
+ }
+
+ private void addEntry(Cache cache){
+ CacheModel model = new CacheModel(cache.getNativeCache().getClass().getName());
+ getCaches().put(cache.getName(), model);
+ }
+
+ public Map getCaches() {
+ return caches;
+ }
+}
diff --git a/src/main/java/org/sathyabodh/actuator/cache/model/CacheModel.java b/src/main/java/org/sathyabodh/actuator/cache/model/CacheModel.java
new file mode 100644
index 0000000..c7f3063
--- /dev/null
+++ b/src/main/java/org/sathyabodh/actuator/cache/model/CacheModel.java
@@ -0,0 +1,16 @@
+package org.sathyabodh.actuator.cache.model;
+
+public class CacheModel {
+ private String target;
+
+ public CacheModel(String target){
+ this.target = target;
+ }
+ public String getTarget() {
+ return target;
+ }
+
+ public void setTarget(String target) {
+ this.target = target;
+ }
+}
diff --git a/src/main/java/org/sathyabodh/actuator/quartz/QuartzJobEndPoint.java b/src/main/java/org/sathyabodh/actuator/quartz/QuartzJobEndPoint.java
new file mode 100644
index 0000000..f8c288e
--- /dev/null
+++ b/src/main/java/org/sathyabodh/actuator/quartz/QuartzJobEndPoint.java
@@ -0,0 +1,132 @@
+package org.sathyabodh.actuator.quartz;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.quartz.JobDetail;
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.impl.matchers.GroupMatcher;
+import org.sathyabodh.actuator.quartz.exception.UnsupportStateChangeException;
+import org.sathyabodh.actuator.quartz.model.GroupModel;
+import org.sathyabodh.actuator.quartz.model.JobDetailModel;
+import org.sathyabodh.actuator.quartz.model.JobModel;
+import org.sathyabodh.actuator.quartz.service.TriggerModelBuilder;
+import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
+import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
+import org.springframework.boot.actuate.endpoint.annotation.Selector;
+import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
+import org.springframework.lang.Nullable;
+
+@Endpoint(id = "quartz-jobs")
+public class QuartzJobEndPoint {
+
+ private Scheduler scheduler;
+
+ private TriggerModelBuilder triggerModelBuilder = new TriggerModelBuilder();
+
+ public QuartzJobEndPoint(Scheduler scheduler){
+ this.scheduler = scheduler;
+ }
+
+ @ReadOperation
+ public GroupModel listJobs(@Nullable String group, @Nullable String name) throws SchedulerException {
+ try {
+ if (name != null && group != null) {
+ JobModel model = createJobModel(new JobKey(name, group));
+ if (model == null) {
+ return null;
+ }
+ GroupModel jobGroupModel = new GroupModel<>();
+ jobGroupModel.add(group, model);
+ return jobGroupModel;
+ }
+ GroupMatcher jobGroupMatcher = group == null ?
+ GroupMatcher.anyJobGroup():GroupMatcher.jobGroupEquals(group);
+
+ Set jobKeys = scheduler.getJobKeys(jobGroupMatcher);
+ if(name != null){
+ jobKeys = jobKeys.stream().filter(key->name.equals(key.getName())).collect(Collectors.toSet());
+ }
+ if (jobKeys == null || jobKeys.isEmpty()) {
+ return null;
+ }
+ GroupModel jobGroupModel = new GroupModel<>();
+ jobKeys.forEach(key->{JobModel model = createJobModel(key);
+ jobGroupModel.add(key.getGroup(), model);
+ });
+ return jobGroupModel;
+ } catch (SchedulerException e) {
+ throw e;
+ }
+ }
+
+ @ReadOperation
+ public JobDetailModel getJobDetail(@Selector String group, @Selector String name) throws SchedulerException{
+ JobDetail jobDetail = scheduler.getJobDetail(new JobKey(name, group));
+ JobDetailModel model = new JobDetailModel();
+ copyJobDetailModel(jobDetail, model);
+ model.setGroup(jobDetail.getKey().getGroup());
+ model.setTriggers(triggerModelBuilder.buildTriggerDetailModel(scheduler, jobDetail.getKey()));
+ return model;
+ }
+
+ private JobModel createJobModel(JobKey key){
+ try {
+ JobDetail jobDetail = scheduler.getJobDetail(key);
+ if (jobDetail == null) {
+ return null;
+ }
+ JobModel model = new JobModel();
+ copyJobDetailModel(jobDetail, model);
+ return model;
+
+ } catch (SchedulerException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ private void copyJobDetailModel(JobDetail jobDetail,JobModel model) {
+ model.setName(jobDetail.getKey().getName());
+ model.setDurable(jobDetail.isDurable());
+ model.setConcurrentDisallowed(jobDetail.isConcurrentExectionDisallowed());
+ model.setJobClass(jobDetail.getJobClass().getName());
+ }
+
+ @WriteOperation
+ public boolean modifyJobStatus(@Selector String group, @Selector String name, @Selector String state)
+ throws SchedulerException {
+ JobKey jobKey = new JobKey(name, group);
+ JobDetail detail = scheduler.getJobDetail(jobKey);
+ if (detail == null)
+ return false;
+ else if (QuartzState.PAUSE.equals(state)) {
+ scheduler.pauseJob(jobKey);
+ } else if (QuartzState.RESUME.equals(state)) {
+ scheduler.resumeJob(jobKey);
+ } else {
+ throw new UnsupportStateChangeException(String.format("unsupported state change. state:[%s]", state));
+ }
+ return true;
+ }
+
+ @WriteOperation
+ public boolean modifyJobsStatus(@Selector String group, @Selector String state) throws SchedulerException{
+ GroupMatcher jobGroupMatcher = GroupMatcher.jobGroupEquals(group);
+ Set jobKeys = scheduler.getJobKeys(jobGroupMatcher);
+ if (jobKeys == null || jobKeys.isEmpty()) {
+ return false;
+ }
+ else if (QuartzState.PAUSE.equals(state)) {
+ scheduler.pauseJobs(jobGroupMatcher);
+ } else if (QuartzState.RESUME.equals(state)) {
+ scheduler.resumeJobs(jobGroupMatcher);
+ } else {
+ throw new UnsupportStateChangeException(String.format("unsupported state change. state:[%s]", state));
+ }
+ return true;
+ }
+
+}
diff --git a/src/main/java/org/sathyabodh/actuator/quartz/QuartzJobEndPointWebExtension.java b/src/main/java/org/sathyabodh/actuator/quartz/QuartzJobEndPointWebExtension.java
new file mode 100644
index 0000000..e3ad360
--- /dev/null
+++ b/src/main/java/org/sathyabodh/actuator/quartz/QuartzJobEndPointWebExtension.java
@@ -0,0 +1,39 @@
+package org.sathyabodh.actuator.quartz;
+
+import org.quartz.SchedulerException;
+import org.sathyabodh.actuator.quartz.exception.UnsupportStateChangeException;
+import org.springframework.boot.actuate.endpoint.annotation.Selector;
+import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
+import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
+import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
+
+@EndpointWebExtension(endpoint=QuartzJobEndPoint.class)
+public class QuartzJobEndPointWebExtension {
+
+ private QuartzJobEndPoint qurtzJobEndPoint ;
+
+ public QuartzJobEndPointWebExtension(QuartzJobEndPoint qurtzJobEndPoint){
+ this.qurtzJobEndPoint = qurtzJobEndPoint;
+ }
+
+ @WriteOperation
+ public WebEndpointResponse> modifyJobStatus(@Selector String group, @Selector String name, @Selector String state) throws SchedulerException {
+ try{
+ boolean isSucess = qurtzJobEndPoint.modifyJobStatus(group, name, state);
+ int status = isSucess ? WebEndpointResponse.STATUS_OK : WebEndpointResponse.STATUS_NOT_FOUND;
+ return new WebEndpointResponse<>(status);
+ }catch(UnsupportStateChangeException e){
+ return new WebEndpointResponse<>(WebEndpointResponse.STATUS_BAD_REQUEST);
+ }
+ }
+ @WriteOperation
+ public WebEndpointResponse> modifyJobsStatus(@Selector String group,@Selector String state) throws SchedulerException {
+ try{
+ boolean isSucess = qurtzJobEndPoint.modifyJobsStatus(group, state);
+ int status = isSucess ? WebEndpointResponse.STATUS_OK : WebEndpointResponse.STATUS_NOT_FOUND;
+ return new WebEndpointResponse<>(status);
+ }catch(UnsupportStateChangeException e){
+ return new WebEndpointResponse<>(WebEndpointResponse.STATUS_BAD_REQUEST);
+ }
+ }
+}
diff --git a/src/main/java/org/sathyabodh/actuator/quartz/QuartzState.java b/src/main/java/org/sathyabodh/actuator/quartz/QuartzState.java
new file mode 100644
index 0000000..943f000
--- /dev/null
+++ b/src/main/java/org/sathyabodh/actuator/quartz/QuartzState.java
@@ -0,0 +1,6 @@
+package org.sathyabodh.actuator.quartz;
+
+public interface QuartzState {
+ String PAUSE="pause";
+ String RESUME="resume";
+}
diff --git a/src/main/java/org/sathyabodh/actuator/quartz/QuartzTriggerEndPoint.java b/src/main/java/org/sathyabodh/actuator/quartz/QuartzTriggerEndPoint.java
new file mode 100644
index 0000000..1aca309
--- /dev/null
+++ b/src/main/java/org/sathyabodh/actuator/quartz/QuartzTriggerEndPoint.java
@@ -0,0 +1,101 @@
+package org.sathyabodh.actuator.quartz;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.Trigger;
+import org.quartz.TriggerKey;
+import org.quartz.impl.matchers.GroupMatcher;
+import org.sathyabodh.actuator.quartz.exception.UnsupportStateChangeException;
+import org.sathyabodh.actuator.quartz.model.GroupModel;
+import org.sathyabodh.actuator.quartz.model.TriggerDetailModel;
+import org.sathyabodh.actuator.quartz.service.TriggerModelBuilder;
+import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
+import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
+import org.springframework.boot.actuate.endpoint.annotation.Selector;
+import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
+import org.springframework.lang.Nullable;
+
+@Endpoint(id = "quartz-triggers")
+public class QuartzTriggerEndPoint {
+ private Scheduler scheduler;
+ private TriggerModelBuilder triggerModelBuilder = new TriggerModelBuilder();
+
+ public QuartzTriggerEndPoint(Scheduler scheduler){
+ this.scheduler = scheduler;
+ }
+
+ @ReadOperation
+ public GroupModel listTriggers(@Nullable String group, @Nullable String name) throws SchedulerException {
+ try {
+ if (name != null && group != null) {
+ TriggerDetailModel model = triggerModelBuilder.buildTriggerDetailModel(scheduler, new TriggerKey(name, group));
+ if (model == null) {
+ return null;
+ }
+ GroupModel groupModel = new GroupModel<>();
+ groupModel.add(group, model);
+ return groupModel;
+ }
+ GroupMatcher triggerGroupMatcher = group == null ?
+ GroupMatcher.anyTriggerGroup():GroupMatcher.triggerGroupEquals(group);
+ Set triggerKeys = scheduler.getTriggerKeys(triggerGroupMatcher);
+ if(name != null){
+ triggerKeys = triggerKeys.stream().filter(key->name.equals(key.getName())).collect(Collectors.toSet());
+ }
+ if (triggerKeys == null || triggerKeys.isEmpty()) {
+ return null;
+ }
+ GroupModel groupModel = new GroupModel<>();
+ triggerKeys.forEach(key->addTriggerDetailModel(groupModel, key));
+ return groupModel;
+ } catch (SchedulerException e) {
+ throw e;
+ }
+ }
+
+ private void addTriggerDetailModel(GroupModel groupModel, TriggerKey key){
+ TriggerDetailModel model;
+ try {
+ model = triggerModelBuilder.buildTriggerDetailModel(scheduler, key);
+ groupModel.add(key.getGroup(), model);
+ } catch (SchedulerException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @WriteOperation
+ public boolean modifyTriggerStatus(@Selector String group, @Selector String name, @Selector String state)
+ throws SchedulerException {
+ TriggerKey triggerKey = new TriggerKey(name, group);
+ Trigger trigger = scheduler.getTrigger(triggerKey);
+ if (trigger == null)
+ return false;
+ else if (QuartzState.PAUSE.equals(state)) {
+ scheduler.pauseTrigger(triggerKey);
+ } else if (QuartzState.RESUME.equals(state)) {
+ scheduler.resumeTrigger(triggerKey);
+ } else {
+ throw new UnsupportStateChangeException(String.format("unsupported state change. state:[%s]", state));
+ }
+ return true;
+ }
+
+ @WriteOperation
+ public boolean modifyTriggersStatus(@Selector String group, @Selector String state) throws SchedulerException {
+ GroupMatcher triggerGroupMatcher = GroupMatcher.triggerGroupEquals(group);
+ Set triggerKeys = scheduler.getTriggerKeys(triggerGroupMatcher);
+ if (triggerKeys == null || triggerKeys.isEmpty()) {
+ return false;
+ } else if (QuartzState.PAUSE.equals(state)) {
+ scheduler.pauseTriggers(triggerGroupMatcher);
+ } else if (QuartzState.RESUME.equals(state)) {
+ scheduler.resumeTriggers((triggerGroupMatcher));
+ } else {
+ throw new UnsupportStateChangeException(String.format("unsupported state change. state:[%s]", state));
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/org/sathyabodh/actuator/quartz/QuartzTriggerEndPointWebExtension.java b/src/main/java/org/sathyabodh/actuator/quartz/QuartzTriggerEndPointWebExtension.java
new file mode 100644
index 0000000..f7a2cfa
--- /dev/null
+++ b/src/main/java/org/sathyabodh/actuator/quartz/QuartzTriggerEndPointWebExtension.java
@@ -0,0 +1,40 @@
+package org.sathyabodh.actuator.quartz;
+
+import org.quartz.SchedulerException;
+import org.sathyabodh.actuator.quartz.exception.UnsupportStateChangeException;
+import org.springframework.boot.actuate.endpoint.annotation.Selector;
+import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
+import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
+import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
+
+@EndpointWebExtension(endpoint=QuartzTriggerEndPoint.class)
+public class QuartzTriggerEndPointWebExtension {
+
+ private QuartzTriggerEndPoint qurtzTriggerEndPoint ;
+
+ public QuartzTriggerEndPointWebExtension(QuartzTriggerEndPoint qurtzTriggerEndPoint){
+ this.qurtzTriggerEndPoint = qurtzTriggerEndPoint;
+ }
+
+ @WriteOperation
+ public WebEndpointResponse> modifyTriggerStatus(@Selector String group, @Selector String name, @Selector String state) throws SchedulerException {
+ try{
+ boolean isSucess = qurtzTriggerEndPoint.modifyTriggerStatus(group, name, state);
+ int status = isSucess ? WebEndpointResponse.STATUS_OK : WebEndpointResponse.STATUS_NOT_FOUND;
+ return new WebEndpointResponse<>(status);
+ }catch(UnsupportStateChangeException e){
+ return new WebEndpointResponse<>(WebEndpointResponse.STATUS_BAD_REQUEST);
+ }
+ }
+ @WriteOperation
+ public WebEndpointResponse> modifyTriggersStatus(@Selector String group,@Selector String state) throws SchedulerException {
+ try{
+ boolean isSucess = qurtzTriggerEndPoint.modifyTriggersStatus(group, state);
+ int status = isSucess ? WebEndpointResponse.STATUS_OK : WebEndpointResponse.STATUS_NOT_FOUND;
+ return new WebEndpointResponse<>(status);
+ }catch(UnsupportStateChangeException e){
+ return new WebEndpointResponse<>(WebEndpointResponse.STATUS_BAD_REQUEST);
+ }
+ }
+
+}
diff --git a/src/main/java/org/sathyabodh/actuator/quartz/exception/UnsupportStateChangeException.java b/src/main/java/org/sathyabodh/actuator/quartz/exception/UnsupportStateChangeException.java
new file mode 100644
index 0000000..708b70c
--- /dev/null
+++ b/src/main/java/org/sathyabodh/actuator/quartz/exception/UnsupportStateChangeException.java
@@ -0,0 +1,14 @@
+package org.sathyabodh.actuator.quartz.exception;
+
+public class UnsupportStateChangeException extends RuntimeException {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ public UnsupportStateChangeException(String message){
+ super(message);
+ }
+
+}
diff --git a/src/main/java/org/sathyabodh/actuator/quartz/model/GroupModel.java b/src/main/java/org/sathyabodh/actuator/quartz/model/GroupModel.java
new file mode 100644
index 0000000..41d4fea
--- /dev/null
+++ b/src/main/java/org/sathyabodh/actuator/quartz/model/GroupModel.java
@@ -0,0 +1,25 @@
+package org.sathyabodh.actuator.quartz.model;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class GroupModel {
+
+ private Map> groups = new HashMap<>();
+
+ public void add(String group, T model){
+ List models = getGroups().get(group);
+ if(models == null){
+ models = new ArrayList<>();
+ getGroups().put(group, models);
+ }
+ models.add(model);
+ }
+
+ public Map> getGroups() {
+ return groups;
+ }
+
+}
diff --git a/src/main/java/org/sathyabodh/actuator/quartz/model/JobDetailModel.java b/src/main/java/org/sathyabodh/actuator/quartz/model/JobDetailModel.java
new file mode 100644
index 0000000..5979056
--- /dev/null
+++ b/src/main/java/org/sathyabodh/actuator/quartz/model/JobDetailModel.java
@@ -0,0 +1,21 @@
+package org.sathyabodh.actuator.quartz.model;
+
+import java.util.List;
+
+public class JobDetailModel extends JobModel{
+ private String group;
+ private List triggers;
+
+ public String getGroup() {
+ return group;
+ }
+ public void setGroup(String group) {
+ this.group = group;
+ }
+ public List getTriggers() {
+ return triggers;
+ }
+ public void setTriggers(List triggers) {
+ this.triggers = triggers;
+ }
+}
diff --git a/src/main/java/org/sathyabodh/actuator/quartz/model/JobModel.java b/src/main/java/org/sathyabodh/actuator/quartz/model/JobModel.java
new file mode 100644
index 0000000..28b7d90
--- /dev/null
+++ b/src/main/java/org/sathyabodh/actuator/quartz/model/JobModel.java
@@ -0,0 +1,33 @@
+package org.sathyabodh.actuator.quartz.model;
+
+
+public class JobModel {
+ private String name;
+ private boolean isConcurrentDisallowed;
+ private boolean isDurable;
+ private String jobClass;
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+ public boolean isConcurrentDisallowed() {
+ return isConcurrentDisallowed;
+ }
+ public void setConcurrentDisallowed(boolean isConcurrentDisallowed) {
+ this.isConcurrentDisallowed = isConcurrentDisallowed;
+ }
+ public boolean isDurable() {
+ return isDurable;
+ }
+ public void setDurable(boolean isDurable) {
+ this.isDurable = isDurable;
+ }
+ public String getJobClass() {
+ return jobClass;
+ }
+ public void setJobClass(String jobClass) {
+ this.jobClass = jobClass;
+ }
+}
diff --git a/src/main/java/org/sathyabodh/actuator/quartz/model/TriggerDetailModel.java b/src/main/java/org/sathyabodh/actuator/quartz/model/TriggerDetailModel.java
new file mode 100644
index 0000000..88ae55e
--- /dev/null
+++ b/src/main/java/org/sathyabodh/actuator/quartz/model/TriggerDetailModel.java
@@ -0,0 +1,64 @@
+package org.sathyabodh.actuator.quartz.model;
+
+import java.util.Date;
+
+public class TriggerDetailModel{
+ private String name;
+ private Date nextFireTime;
+ private Date previousFireTime;
+ private Date startTime;
+ private Date endTime;
+ private String group;
+ private String state;
+ private String jobKey;
+
+ public void setState(String state) {
+ this.state = state;
+ }
+ public String getState() {
+ return state;
+ }
+
+ public String getJobKey() {
+ return jobKey;
+ }
+ public void setJobKey(String jobKey) {
+ this.jobKey = jobKey;
+ }
+ public String getGroup() {
+ return group;
+ }
+ public void setGroup(String group) {
+ this.group = group;
+ }
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+ public Date getNextFireTime() {
+ return nextFireTime;
+ }
+ public void setNextFireTime(Date nextFireTime) {
+ this.nextFireTime = nextFireTime;
+ }
+ public Date getPreviousFireTime() {
+ return previousFireTime;
+ }
+ public void setPreviousFireTime(Date previousFireTime) {
+ this.previousFireTime = previousFireTime;
+ }
+ public Date getStartTime() {
+ return startTime;
+ }
+ public void setStartTime(Date startTime) {
+ this.startTime = startTime;
+ }
+ public Date getEndTime() {
+ return endTime;
+ }
+ public void setEndTime(Date endTime) {
+ this.endTime = endTime;
+ }
+}
diff --git a/src/main/java/org/sathyabodh/actuator/quartz/model/TriggerModel.java b/src/main/java/org/sathyabodh/actuator/quartz/model/TriggerModel.java
new file mode 100644
index 0000000..7abe96a
--- /dev/null
+++ b/src/main/java/org/sathyabodh/actuator/quartz/model/TriggerModel.java
@@ -0,0 +1,56 @@
+package org.sathyabodh.actuator.quartz.model;
+
+import java.util.Date;
+
+public class TriggerModel {
+ private String group;
+ private String name;
+ private String state;
+ private Date nextFireTime;
+ private Date previousFireTime;
+ private Date startTime;
+ private Date endTime;
+
+ public String getGroup() {
+ return group;
+ }
+ public void setGroup(String group) {
+ this.group = group;
+ }
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+ public String getState() {
+ return state;
+ }
+ public void setState(String state) {
+ this.state = state;
+ }
+ public Date getNextFireTime() {
+ return nextFireTime;
+ }
+ public void setNextFireTime(Date nextFireTime) {
+ this.nextFireTime = nextFireTime;
+ }
+ public Date getPreviousFireTime() {
+ return previousFireTime;
+ }
+ public void setPreviousFireTime(Date previousFireTime) {
+ this.previousFireTime = previousFireTime;
+ }
+ public Date getStartTime() {
+ return startTime;
+ }
+ public void setStartTime(Date startTime) {
+ this.startTime = startTime;
+ }
+ public Date getEndTime() {
+ return endTime;
+ }
+ public void setEndTime(Date endTime) {
+ this.endTime = endTime;
+ }
+}
diff --git a/src/main/java/org/sathyabodh/actuator/quartz/service/TriggerModelBuilder.java b/src/main/java/org/sathyabodh/actuator/quartz/service/TriggerModelBuilder.java
new file mode 100644
index 0000000..4cc9f4b
--- /dev/null
+++ b/src/main/java/org/sathyabodh/actuator/quartz/service/TriggerModelBuilder.java
@@ -0,0 +1,45 @@
+package org.sathyabodh.actuator.quartz.service;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.Trigger;
+import org.quartz.Trigger.TriggerState;
+import org.quartz.TriggerKey;
+import org.sathyabodh.actuator.quartz.model.TriggerDetailModel;
+
+public class TriggerModelBuilder {
+
+ public List buildTriggerDetailModel(Scheduler scheduler, JobKey jobKey) throws SchedulerException{
+ List extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
+ return triggers.stream().map(t->buildTriggerDetailModel(scheduler, t)).collect(Collectors.toList());
+ }
+
+ public TriggerDetailModel buildTriggerDetailModel(Scheduler scheduler, TriggerKey triggerKey) throws SchedulerException{
+ Trigger trigger = scheduler.getTrigger(triggerKey);
+ return buildTriggerDetailModel(scheduler, trigger);
+ }
+
+ private TriggerDetailModel buildTriggerDetailModel(Scheduler scheduler, Trigger trigger){
+ TriggerDetailModel model = new TriggerDetailModel();
+ model.setName(trigger.getKey().getName());
+ model.setNextFireTime(trigger.getNextFireTime());
+ model.setPreviousFireTime(trigger.getPreviousFireTime());
+ model.setStartTime(trigger.getStartTime());
+ model.setEndTime(trigger.getEndTime());
+ model.setGroup(trigger.getKey().getGroup());
+ model.setJobKey(trigger.getJobKey().toString());
+ TriggerState triggerState;
+ try {
+ triggerState = scheduler.getTriggerState(trigger.getKey());
+ model.setState(triggerState.toString());
+ } catch (SchedulerException e) {
+ model.setState("Could not set due to error");
+ }
+ return model;
+ }
+
+}
diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..74a6599
--- /dev/null
+++ b/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,4 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+org.sathyabodh.actuator.autoconfigure.cache.CachesEndPointAutoConfiguration,\
+org.sathyabodh.actuator.autoconfigure.quartz.QurtzJobEndPointAutoConfiguration,\
+org.sathyabodh.actuator.autoconfigure.quartz.QuartzTriggerEndPointAutoConfiguration
\ No newline at end of file
diff --git a/src/test/java/org/sathyabodh/actuator/web/documentation/AbstractQuartzEndPointDocumentationTests.java b/src/test/java/org/sathyabodh/actuator/web/documentation/AbstractQuartzEndPointDocumentationTests.java
new file mode 100644
index 0000000..bce50fc
--- /dev/null
+++ b/src/test/java/org/sathyabodh/actuator/web/documentation/AbstractQuartzEndPointDocumentationTests.java
@@ -0,0 +1,56 @@
+package org.sathyabodh.actuator.web.documentation;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.quartz.Scheduler;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
+import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
+import org.springframework.boot.actuate.autoconfigure.endpoint.web.reactive.WebFluxEndpointManagementContextConfiguration;
+import org.springframework.boot.actuate.autoconfigure.endpoint.web.servlet.WebMvcEndpointManagementContextConfiguration;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
+import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
+import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
+import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
+import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
+import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
+import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.restdocs.JUnitRestDocumentation;
+import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+public class AbstractQuartzEndPointDocumentationTests {
+ @Rule
+ public JUnitRestDocumentation documentation = new JUnitRestDocumentation("target/generated-snippets");
+ @Autowired
+ protected WebApplicationContext context;
+
+ protected MockMvc mockMvc;
+
+ @MockBean
+ protected Scheduler scheduler;
+
+ @Before
+ public void setUp() {
+ this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
+ .apply(MockMvcRestDocumentation.documentationConfiguration(this.documentation)).build();
+ }
+
+ @Configuration
+ @ImportAutoConfiguration({ JacksonAutoConfiguration.class,
+ HttpMessageConvertersAutoConfiguration.class, WebMvcAutoConfiguration.class,
+ DispatcherServletAutoConfiguration.class, EndpointAutoConfiguration.class,
+ WebEndpointAutoConfiguration.class,
+ WebMvcEndpointManagementContextConfiguration.class,
+ WebFluxEndpointManagementContextConfiguration.class,
+ PropertyPlaceholderAutoConfiguration.class, WebFluxAutoConfiguration.class,
+ HttpHandlerAutoConfiguration.class })
+static class BaseDocumentationConfiguration {
+}
+
+}
diff --git a/src/test/java/org/sathyabodh/actuator/web/documentation/QuartzJobEndPointDocumentationTests.java b/src/test/java/org/sathyabodh/actuator/web/documentation/QuartzJobEndPointDocumentationTests.java
new file mode 100644
index 0000000..812d719
--- /dev/null
+++ b/src/test/java/org/sathyabodh/actuator/web/documentation/QuartzJobEndPointDocumentationTests.java
@@ -0,0 +1,195 @@
+package org.sathyabodh.actuator.web.documentation;
+
+import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
+import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
+import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
+import static org.springframework.restdocs.request.RequestDocumentation.requestParameters;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.quartz.CronScheduleBuilder;
+import org.quartz.DisallowConcurrentExecution;
+import org.quartz.Job;
+import org.quartz.JobBuilder;
+import org.quartz.JobDetail;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.quartz.JobKey;
+import org.quartz.ScheduleBuilder;
+import org.quartz.Scheduler;
+import org.quartz.Trigger;
+import org.quartz.Trigger.TriggerState;
+import org.quartz.TriggerBuilder;
+import org.quartz.TriggerKey;
+import org.quartz.impl.matchers.GroupMatcher;
+import org.sathyabodh.actuator.quartz.QuartzJobEndPoint;
+import org.sathyabodh.actuator.quartz.QuartzJobEndPointWebExtension;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@TestPropertySource(properties={
+ "management.endpoints.web.exposure.include=*",
+ "management.endpoints.web.basePath=/actuator"
+})
+public class QuartzJobEndPointDocumentationTests extends AbstractQuartzEndPointDocumentationTests {
+
+ @Test
+ public void testQuartzJobPause() throws Exception {
+ JobKey jobKey = new JobKey("myjob", "myjobgroup");
+ JobDetail jobDetail = JobBuilder.newJob(Job.class).withIdentity(jobKey).build();
+ Mockito.when(scheduler.getJobDetail(jobKey)).thenReturn(jobDetail);
+ Mockito.doNothing().when(scheduler).pauseJob(jobKey);
+ this.mockMvc.perform(MockMvcRequestBuilders.post("/actuator/quartz-jobs/myjobgroup/myjob/pause"))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcRestDocumentation.document("quartz-jobs/named/job/pause"));
+ }
+
+ @Test
+ public void testQuartzJobResume() throws Exception {
+ JobKey jobKey = new JobKey("myjob", "myjobgroup");
+ JobDetail jobDetail = JobBuilder.newJob(Job.class).withIdentity(jobKey).build();
+ Mockito.when(scheduler.getJobDetail(jobKey)).thenReturn(jobDetail);
+ Mockito.doNothing().when(scheduler).pauseJob(jobKey);
+ this.mockMvc.perform(MockMvcRequestBuilders.post("/actuator/quartz-jobs/myjobgroup/myjob/resume"))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcRestDocumentation.document("quartz-jobs/named/job/resume"));
+ }
+
+ @Test
+ public void testQuartzJobGroupPause() throws Exception {
+ JobKey jobKey = new JobKey("myjob", "myjobgroup");
+ Set jobKeys = new HashSet<>();
+ jobKeys.add(jobKey);
+ GroupMatcher jobGroupMatcher = GroupMatcher.jobGroupEquals("myjobgroup");
+ Mockito.when(scheduler.getJobKeys(jobGroupMatcher)).thenReturn(jobKeys);
+ Mockito.doNothing().when(scheduler).pauseJobs(jobGroupMatcher);
+ this.mockMvc.perform(MockMvcRequestBuilders.post("/actuator/quartz-jobs/myjobgroup/pause"))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcRestDocumentation.document("quartz-jobs/named/jobgroup/pause"));
+ }
+
+ @Test
+ public void testQuartzJobGroupResume() throws Exception {
+ JobKey jobKey = new JobKey("myjob", "myjobgroup");
+ Set jobKeys = new HashSet<>();
+ jobKeys.add(jobKey);
+ GroupMatcher jobGroupMatcher = GroupMatcher.jobGroupEquals("myjobgroup");
+ Mockito.when(scheduler.getJobKeys(jobGroupMatcher)).thenReturn(jobKeys);
+ Mockito.doNothing().when(scheduler).pauseJobs(jobGroupMatcher);
+ this.mockMvc.perform(MockMvcRequestBuilders.post("/actuator/quartz-jobs/myjobgroup/resume"))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcRestDocumentation.document("quartz-jobs/named/jobgroup/resume"));
+ }
+
+ @Test
+ public void testQuartzJobList() throws Exception {
+ int i = 1;
+ Set jobKeys = new HashSet<>();
+ List jobDetails = new ArrayList<>();
+ while (i <= 3) {
+ JobKey jobKey = new JobKey("myjob_" + i, "myjobgroup");
+ JobDetail jobDetail = JobBuilder.newJob(MockNonConcurrentJob.class).withIdentity(jobKey).storeDurably()
+ .build();
+ ++i;
+ jobKeys.add(jobKey);
+ jobDetails.add(jobDetail);
+ }
+
+ GroupMatcher jobGroupMatcher = GroupMatcher.jobGroupEquals("myjobgroup");
+ Mockito.when(scheduler.getJobKeys(jobGroupMatcher)).thenReturn(jobKeys);
+
+ Mockito.when(scheduler.getJobDetail(Mockito.any(JobKey.class))).thenReturn(jobDetails.get(0));
+ this.mockMvc.perform(MockMvcRequestBuilders.get("/actuator/quartz-jobs?group=myjobgroup&name=myjob_1"))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcRestDocumentation.document("quartz-jobs/list",
+ requestParameters(parameterWithName("group").description("Name of the job group").optional(),
+ parameterWithName("name").description("Name of the job").optional()),
+ responseFields(fieldWithPath("groups").description("Job group keyed by group name"),
+ fieldWithPath("groups.*.[].name").description("Name of the job"),
+ fieldWithPath("groups.*.[].concurrentDisallowed")
+ .description("Indicates concurrent execution of job is allowed"),
+ fieldWithPath("groups.*.[].durable").description("Indicates job is durable"),
+ fieldWithPath("groups.*.[].jobClass").description("Class name of the job"))));
+ }
+
+ @Test
+ public void testGetJobDetails() throws Exception {
+ JobKey jobKey = new JobKey("myjob", "myjobgroup");
+ JobDetail jobDetail = JobBuilder.newJob(MockNonConcurrentJob.class).withIdentity(jobKey).storeDurably().build();
+
+ Trigger trigger1 = TriggerBuilder.newTrigger().forJob(jobDetail).withIdentity("trigger_1", "trigger_group1")
+ .startAt(new Date()).build();
+
+ ScheduleBuilder> builder = CronScheduleBuilder.cronSchedule("0 0 23 * * ?")
+ .withMisfireHandlingInstructionFireAndProceed();
+ Trigger trigger2 = TriggerBuilder.newTrigger().forJob(jobDetail).withIdentity("trigger_2", "trigger_group1")
+ .withSchedule(builder).build();
+
+ List triggers = new ArrayList();
+ triggers.add(trigger1);
+ triggers.add(trigger2);
+
+ Mockito.when(scheduler.getJobDetail(Mockito.any(JobKey.class))).thenReturn(jobDetail);
+ Mockito.when(scheduler.getTriggersOfJob(Mockito.any(JobKey.class))).thenReturn(triggers);
+ Mockito.when(scheduler.getTriggerState(Mockito.any(TriggerKey.class))).thenReturn(TriggerState.NORMAL);
+
+ this.mockMvc.perform(MockMvcRequestBuilders.get("/actuator/quartz-jobs/myjobgroup/myjob"))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcRestDocumentation.document("quartz-jobs/named/jobdetails",
+ responseFields(fieldWithPath("name").description("Name of the job"),
+ fieldWithPath("group").description("Group name of the job"),
+ fieldWithPath("concurrentDisallowed")
+ .description("Indicates concurrent execution of job is allowed"),
+ fieldWithPath("durable").description("Indicates job is durable"),
+ fieldWithPath("jobClass").description("Class name of the job"),
+ fieldWithPath("triggers").description("Triggers associated with job"),
+ fieldWithPath("triggers.[].name").description("Name of the trigger"),
+ fieldWithPath("triggers.[].nextFireTime").description("Next fire time of the trigger"),
+ fieldWithPath("triggers.[].previousFireTime")
+ .description("Previous fire time of the trigger"),
+ fieldWithPath("triggers.[].startTime").description("Start time of the trigger"),
+ fieldWithPath("triggers.[].endTime").description("End time of the trigger"),
+ fieldWithPath("triggers.[].group").description("Trigger's group"),
+ fieldWithPath("triggers.[].state").description("State of trigger ex:PAUSED,NORMAL"),
+ fieldWithPath("triggers.[].jobKey").description("Associated Job key of the trigger"))));
+ }
+
+ @DisallowConcurrentExecution
+ class MockNonConcurrentJob implements Job {
+
+ @Override
+ public void execute(JobExecutionContext context) throws JobExecutionException {
+ }
+ }
+
+ @Configuration
+ @Import(BaseDocumentationConfiguration.class)
+ static class QuartzJobEndPointConfiguration {
+ @Bean
+ public QuartzJobEndPoint quartzJobEndPoint(Scheduler scheduler) {
+ return new QuartzJobEndPoint(scheduler);
+ }
+
+ @Bean
+ public QuartzJobEndPointWebExtension quartzJobEndPointWebExtension(QuartzJobEndPoint quartzJobEndPoint) {
+ return new QuartzJobEndPointWebExtension(quartzJobEndPoint);
+ }
+
+ }
+}
diff --git a/src/test/java/org/sathyabodh/actuator/web/documentation/QuartzTriggerEndPointDocumentationTests.java b/src/test/java/org/sathyabodh/actuator/web/documentation/QuartzTriggerEndPointDocumentationTests.java
new file mode 100644
index 0000000..42aaba5
--- /dev/null
+++ b/src/test/java/org/sathyabodh/actuator/web/documentation/QuartzTriggerEndPointDocumentationTests.java
@@ -0,0 +1,108 @@
+package org.sathyabodh.actuator.web.documentation;
+
+import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
+import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
+import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
+import static org.springframework.restdocs.request.RequestDocumentation.requestParameters;
+
+import java.util.Date;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.quartz.JobBuilder;
+import org.quartz.JobDetail;
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.quartz.Trigger;
+import org.quartz.Trigger.TriggerState;
+import org.quartz.TriggerBuilder;
+import org.quartz.TriggerKey;
+import org.sathyabodh.actuator.quartz.QuartzTriggerEndPoint;
+import org.sathyabodh.actuator.quartz.QuartzTriggerEndPointWebExtension;
+import org.sathyabodh.actuator.web.documentation.QuartzJobEndPointDocumentationTests.MockNonConcurrentJob;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@TestPropertySource(properties={
+ "management.endpoints.web.exposure.include=*",
+ "management.endpoints.web.basePath=/actuator"
+})
+public class QuartzTriggerEndPointDocumentationTests extends AbstractQuartzEndPointDocumentationTests {
+
+ @Test
+ public void testTriggerListing() throws Exception {
+
+ JobDetail jobDetail = JobBuilder.newJob(MockNonConcurrentJob.class)
+ .withIdentity(new JobKey("myjob", "myjobGroup")).storeDurably().build();
+ Trigger trigger1 = TriggerBuilder.newTrigger().forJob(jobDetail).withIdentity("trigger_1", "trigger_group1")
+ .startAt(new Date()).build();
+ Mockito.when(scheduler.getTrigger(Mockito.any(TriggerKey.class))).thenReturn(trigger1);
+ Mockito.when(scheduler.getTriggerState(Mockito.any(TriggerKey.class))).thenReturn(TriggerState.NORMAL);
+
+ this.mockMvc.perform(MockMvcRequestBuilders.get("/actuator/quartz-triggers?group=myjobgroup&name=myjob_"))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcRestDocumentation.document("quartz-triggers/list",
+ requestParameters(parameterWithName("group").description("Name of the trigger").optional(),
+ parameterWithName("name").description("Name of the trigger").optional()),
+ responseFields(fieldWithPath("groups").description("Trigger group keyed by group name"),
+ fieldWithPath("groups.*.[].name").description("Name of the trigger"),
+ fieldWithPath("groups.*.[].nextFireTime").description("Next fire time of the trigger"),
+ fieldWithPath("groups.*.[].previousFireTime")
+ .description("Previous fire time of the trigger"),
+ fieldWithPath("groups.*.[].startTime").description("Start time of the trigger"),
+ fieldWithPath("groups.*.[].endTime").description("End time of the trigger"),
+ fieldWithPath("groups.*.[].group").description("Trigger's group"),
+ fieldWithPath("groups.*.[].state").description("State of trigger ex:PAUSED,NORMAL"),
+ fieldWithPath("groups.*.[].jobKey").description("Associated Job key of the trigger"))));
+ }
+
+ @Test
+ public void testTriggerPause() throws Exception {
+ JobDetail jobDetail = JobBuilder.newJob(MockNonConcurrentJob.class)
+ .withIdentity(new JobKey("myjob", "myjobGroup")).storeDurably().build();
+ Trigger trigger1 = TriggerBuilder.newTrigger().forJob(jobDetail).withIdentity("trigger_1", "trigger_group1")
+ .startAt(new Date()).build();
+ Mockito.when(scheduler.getTrigger(Mockito.any(TriggerKey.class))).thenReturn(trigger1);
+ this.mockMvc.perform(MockMvcRequestBuilders.post("/actuator/quartz-triggers/mytriggergroup/mytrigger/pause"))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcRestDocumentation.document("quartz-triggers/named/trigger/pause"));
+
+ }
+
+ @Test
+ public void testTriggerResume() throws Exception {
+ JobDetail jobDetail = JobBuilder.newJob(MockNonConcurrentJob.class)
+ .withIdentity(new JobKey("myjob", "myjobGroup")).storeDurably().build();
+ Trigger trigger1 = TriggerBuilder.newTrigger().forJob(jobDetail).withIdentity("trigger_1", "trigger_group1")
+ .startAt(new Date()).build();
+ Mockito.when(scheduler.getTrigger(Mockito.any(TriggerKey.class))).thenReturn(trigger1);
+ this.mockMvc.perform(MockMvcRequestBuilders.post("/actuator/quartz-triggers/mytriggergroup/mytrigger/resume"))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcRestDocumentation.document("quartz-triggers/named/trigger/resume"));
+ }
+
+ @Configuration
+ @Import(BaseDocumentationConfiguration.class)
+ static class QuartzTriggerEndPointConfiguration {
+ @Bean
+ public QuartzTriggerEndPoint quartzTriggerEndPoint(Scheduler scheduler) {
+ return new QuartzTriggerEndPoint(scheduler);
+ }
+
+ @Bean
+ QuartzTriggerEndPointWebExtension quartzTriggerEndPointWebExtension(
+ QuartzTriggerEndPoint quartzTriggerEndPoint) {
+ return new QuartzTriggerEndPointWebExtension(quartzTriggerEndPoint);
+ }
+ }
+}