diff --git a/qqq-backend-core/pom.xml b/qqq-backend-core/pom.xml
index 58191fa6..f8da0a96 100644
--- a/qqq-backend-core/pom.xml
+++ b/qqq-backend-core/pom.xml
@@ -163,6 +163,17 @@
1.12.321
+
+ org.quartz-scheduler
+ quartz
+ 2.3.2
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.9
+
+
org.apache.maven.plugins
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/quartz/QuartzRunProcessJob.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/quartz/QuartzRunProcessJob.java
new file mode 100644
index 00000000..a5bb27f3
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/quartz/QuartzRunProcessJob.java
@@ -0,0 +1,77 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2023. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.scheduler.quartz;
+
+
+import java.io.Serializable;
+import java.util.Map;
+import com.kingsrook.qqq.backend.core.context.QContext;
+import com.kingsrook.qqq.backend.core.logging.QLogger;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.scheduler.SchedulerUtils;
+import org.quartz.DisallowConcurrentExecution;
+import org.quartz.Job;
+import org.quartz.JobDataMap;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+@DisallowConcurrentExecution
+public class QuartzRunProcessJob implements Job
+{
+ private static final QLogger LOG = QLogger.getLogger(QuartzRunProcessJob.class);
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException
+ {
+ try
+ {
+ JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
+ String processName = jobDataMap.getString("processName");
+
+ ///////////////////////////////////
+ // todo - variants from job data //
+ ///////////////////////////////////
+ Map backendVariantData = null;
+
+ LOG.debug("Running quartz process", logPair("processName", processName));
+
+ QInstance qInstance = QuartzScheduler.getInstance().getQInstance();
+ SchedulerUtils.runProcess(qInstance, QuartzScheduler.getInstance().getSessionSupplier(), qInstance.getProcess(processName), backendVariantData);
+
+ }
+ finally
+ {
+ QContext.clear();
+ }
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/quartz/QuartzScheduler.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/quartz/QuartzScheduler.java
new file mode 100644
index 00000000..5a8385f6
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/quartz/QuartzScheduler.java
@@ -0,0 +1,365 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2023. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.scheduler.quartz;
+
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Supplier;
+import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
+import com.kingsrook.qqq.backend.core.logging.QLogger;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaData;
+import com.kingsrook.qqq.backend.core.model.session.QSession;
+import com.kingsrook.qqq.backend.core.scheduler.SchedulerUtils;
+import org.quartz.JobBuilder;
+import org.quartz.JobDetail;
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.SimpleScheduleBuilder;
+import org.quartz.Trigger;
+import org.quartz.TriggerBuilder;
+import org.quartz.TriggerKey;
+import org.quartz.impl.StdSchedulerFactory;
+import org.quartz.impl.matchers.GroupMatcher;
+import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class QuartzScheduler
+{
+ private static final QLogger LOG = QLogger.getLogger(QuartzScheduler.class);
+
+ private static QuartzScheduler quartzScheduler = null;
+
+ private final QInstance qInstance;
+ private Supplier sessionSupplier;
+
+ private Scheduler scheduler;
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ private QuartzScheduler(QInstance qInstance, Supplier sessionSupplier)
+ {
+ this.qInstance = qInstance;
+ this.sessionSupplier = sessionSupplier;
+ }
+
+
+
+ /*******************************************************************************
+ ** Singleton initiator...
+ *******************************************************************************/
+ public static QuartzScheduler initInstance(QInstance qInstance, Supplier sessionSupplier)
+ {
+ if(quartzScheduler == null)
+ {
+ quartzScheduler = new QuartzScheduler(qInstance, sessionSupplier);
+ }
+ return (quartzScheduler);
+ }
+
+
+
+ /*******************************************************************************
+ ** Singleton accessor
+ *******************************************************************************/
+ public static QuartzScheduler getInstance()
+ {
+ if(quartzScheduler == null)
+ {
+ throw (new IllegalStateException("QuartzScheduler singleton has not been init'ed."));
+ }
+ return (quartzScheduler);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void start()
+ {
+ try
+ {
+ // Properties properties = new Properties();
+ // properties.put("");
+
+ //////////////////////////////////////////////////
+ // Grab the Scheduler instance from the Factory //
+ //////////////////////////////////////////////////
+ StdSchedulerFactory schedulerFactory = new StdSchedulerFactory();
+ // schedulerFactory.initialize(properties);
+ this.scheduler = schedulerFactory.getScheduler();
+
+ ////////////////////////////////////////
+ // todo - do we get our own property? //
+ ////////////////////////////////////////
+ if(!new QMetaDataVariableInterpreter().getBooleanFromPropertyOrEnvironment("qqq.scheduleManager.enabled", "QQQ_SCHEDULE_MANAGER_ENABLED", true))
+ {
+ LOG.info("Not starting QuartzScheduler per settings.");
+ return;
+ }
+
+ /////////////////////////////////////////////
+ // make sure all of our jobs are scheduled //
+ /////////////////////////////////////////////
+ scheduleAllJobs();
+
+ //////////////////////
+ // and start it off //
+ //////////////////////
+ scheduler.start();
+ }
+ catch(Exception e)
+ {
+ LOG.error("Error starting quartz scheduler", e);
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private void scheduleAllJobs()
+ {
+ for(QProcessMetaData process : qInstance.getProcesses().values())
+ {
+ if(process.getSchedule() != null && SchedulerUtils.allowedToStart(process.getName()))
+ {
+ if(process.getSchedule().getVariantBackend() == null || QScheduleMetaData.RunStrategy.SERIAL.equals(process.getSchedule().getVariantRunStrategy()))
+ {
+ scheduleProcess(process, null);
+ }
+ else
+ {
+ LOG.error("Not yet know how to schedule parallel variant jobs");
+ }
+ }
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private void scheduleProcess(QProcessMetaData process, Map backendVariantData)
+ {
+ try
+ {
+ QScheduleMetaData scheduleMetaData = process.getSchedule();
+ long intervalMillis = Objects.requireNonNullElse(scheduleMetaData.getRepeatMillis(), scheduleMetaData.getRepeatSeconds() * 1000);
+
+ Date startAt = new Date();
+ if(scheduleMetaData.getInitialDelayMillis() != null)
+ {
+ startAt.setTime(startAt.getTime() + scheduleMetaData.getInitialDelayMillis());
+ }
+ if(scheduleMetaData.getInitialDelaySeconds() != null)
+ {
+ startAt.setTime(startAt.getTime() + scheduleMetaData.getInitialDelaySeconds() * 1000);
+ }
+
+ /////////////////////////
+ // Define job instance //
+ /////////////////////////
+ JobKey jobKey = new JobKey(process.getName(), "processes");
+ JobDetail jobDetail = JobBuilder.newJob(QuartzRunProcessJob.class)
+ .withIdentity(jobKey)
+ .storeDurably()
+ .requestRecovery()
+ .build();
+
+ jobDetail.getJobDataMap().put("processName", process.getName());
+
+ ///////////////////////////////////////
+ // Define a Trigger for the schedule //
+ ///////////////////////////////////////
+ Trigger trigger = TriggerBuilder.newTrigger()
+ .withIdentity(new TriggerKey(process.getName(), "processes"))
+ .forJob(jobKey)
+ .withSchedule(SimpleScheduleBuilder.simpleSchedule()
+ .withIntervalInMilliseconds(intervalMillis)
+ .repeatForever())
+ .startAt(startAt)
+ .build();
+
+ ///////////////////////////////////////
+ // Schedule the job with the trigger //
+ ///////////////////////////////////////
+ boolean isJobAlreadyScheduled = isJobAlreadyScheduled(jobKey);
+ if(isJobAlreadyScheduled)
+ {
+ this.scheduler.addJob(jobDetail, true);
+ this.scheduler.rescheduleJob(trigger.getKey(), trigger);
+ LOG.info("Re-scheduled process: " + process.getName());
+ }
+ else
+ {
+ this.scheduler.scheduleJob(jobDetail, trigger);
+ LOG.info("Scheduled new process: " + process.getName());
+ }
+
+ }
+ catch(Exception e)
+ {
+ LOG.warn("Error scheduling process", e, logPair("processName", process.getName()));
+ }
+ }
+
+
+
+ /*******************************************************************************
+ ** todo - probably rewrite this to not re-query quartz each time
+ *******************************************************************************/
+ private boolean isJobAlreadyScheduled(JobKey jobKey) throws SchedulerException
+ {
+ for(String group : scheduler.getJobGroupNames())
+ {
+ for(JobKey testJobKey : scheduler.getJobKeys(GroupMatcher.groupEquals(group)))
+ {
+ if(testJobKey.equals(jobKey))
+ {
+ return (true);
+ }
+ }
+ }
+
+ return (false);
+ }
+
+
+
+ /*
+ private void todo() throws SchedulerException
+ {
+ // https://www.quartz-scheduler.org/documentation/quartz-2.3.0/cookbook/ListJobs.html
+ // Listing all Jobs in the scheduler
+ for(String group : scheduler.getJobGroupNames())
+ {
+ for(JobKey jobKey : scheduler.getJobKeys(GroupMatcher.groupEquals(group)))
+ {
+ System.out.println("Found job identified by: " + jobKey);
+ }
+ }
+
+ // https://www.quartz-scheduler.org/documentation/quartz-2.3.0/cookbook/UpdateJob.html
+ // Update an existing job
+ // Add the new job to the scheduler, instructing it to "replace"
+ // the existing job with the given name and group (if any)
+ JobDetail jobDetail = JobBuilder.newJob(QuartzRunProcessJob.class)
+ .withIdentity("job1", "group1")
+ .build();
+ // store, and set overwrite flag to 'true'
+ scheduler.addJob(jobDetail, true);
+
+ // https://www.quartz-scheduler.org/documentation/quartz-2.3.0/cookbook/UpdateTrigger.html
+ // Define a new Trigger
+ Trigger trigger = TriggerBuilder.newTrigger()
+ .withIdentity("newTrigger", "group1")
+ .startNow()
+ .build();
+
+ // tell the scheduler to remove the old trigger with the given key, and put the new one in its place
+ scheduler.rescheduleJob(new TriggerKey("oldTrigger", "group1"), trigger);
+
+ // https://www.quartz-scheduler.org/documentation/quartz-2.3.0/cookbook/UnscheduleJob.html
+ // Deleting a Job and Unscheduling All of Its Triggers
+ scheduler.deleteJob(new JobKey("job1", "group1"));
+
+ }
+ */
+
+
+
+ /*******************************************************************************
+ ** Getter for qInstance
+ **
+ *******************************************************************************/
+ public QInstance getQInstance()
+ {
+ return qInstance;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for sessionSupplier
+ **
+ *******************************************************************************/
+ public Supplier getSessionSupplier()
+ {
+ return sessionSupplier;
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void pauseAll() throws SchedulerException
+ {
+ this.scheduler.pauseAll();
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void resumeAll() throws SchedulerException
+ {
+ this.scheduler.resumeAll();
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void pauseJob(String jobName, String groupName) throws SchedulerException
+ {
+ this.scheduler.pauseJob(new JobKey(jobName, groupName));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void resumeJob(String jobName, String groupName) throws SchedulerException
+ {
+ this.scheduler.resumeJob(new JobKey(jobName, groupName));
+ }
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/quartz/processes/PauseAllQuartzJobsProcess.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/quartz/processes/PauseAllQuartzJobsProcess.java
new file mode 100644
index 00000000..7d820f08
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/quartz/processes/PauseAllQuartzJobsProcess.java
@@ -0,0 +1,86 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2023. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.scheduler.quartz.processes;
+
+
+import java.util.List;
+import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
+import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.WidgetHtmlLine;
+import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.NoCodeWidgetFrontendComponentMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
+import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzScheduler;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class PauseAllQuartzJobsProcess implements BackendStep, MetaDataProducerInterface
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public QProcessMetaData produce(QInstance qInstance) throws QException
+ {
+ return new QProcessMetaData()
+ .withName(getClass().getSimpleName())
+ .withLabel("Pause All Quartz Jobs")
+ .withStepList(List.of(
+ new QBackendStepMetaData()
+ .withName("execute")
+ .withCode(new QCodeReference(getClass())),
+ new QFrontendStepMetaData()
+ .withName("results")
+ .withComponent(new NoCodeWidgetFrontendComponentMetaData()
+ .withOutput(new WidgetHtmlLine().withVelocityTemplate("All quartz jobs have been paused")))))
+ .withIcon(new QIcon("pause_circle_outline"));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
+ {
+ try
+ {
+ QuartzScheduler.getInstance().pauseAll();
+ }
+ catch(Exception e)
+ {
+ throw (new QException("Error pausing all jobs", e));
+ }
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/quartz/processes/PauseQuartzJobsProcess.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/quartz/processes/PauseQuartzJobsProcess.java
new file mode 100644
index 00000000..0aaff30a
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/quartz/processes/PauseQuartzJobsProcess.java
@@ -0,0 +1,89 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2023. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.scheduler.quartz.processes;
+
+
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
+import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.AbstractLoadStep;
+import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ExtractViaQueryStep;
+import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.NoopTransformStep;
+import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
+import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzScheduler;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class PauseQuartzJobsProcess extends AbstractLoadStep implements MetaDataProducerInterface
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public QProcessMetaData produce(QInstance qInstance) throws QException
+ {
+ String tableName = "QUARTZ_TRIGGERS";
+
+ return StreamedETLWithFrontendProcess.processMetaDataBuilder()
+ .withName(getClass().getSimpleName())
+ .withLabel("Pause Quartz Jobs")
+ .withTableName(tableName)
+ .withSourceTable(tableName)
+ .withDestinationTable(tableName)
+ .withExtractStepClass(ExtractViaQueryStep.class)
+ .withTransformStepClass(NoopTransformStep.class)
+ .withLoadStepClass(getClass())
+ .withIcon(new QIcon("pause_circle_outline"))
+ .getProcessMetaData();
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
+ {
+ try
+ {
+ QuartzScheduler instance = QuartzScheduler.getInstance();
+ for(QRecord record : runBackendStepInput.getRecords())
+ {
+ instance.pauseJob(record.getValueString("JOB_NAME"), record.getValueString("GROUP_NAME"));
+ }
+ }
+ catch(Exception e)
+ {
+ throw (new QException("Error pausing jobs", e));
+ }
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/quartz/processes/ResumeAllQuartzJobsProcess.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/quartz/processes/ResumeAllQuartzJobsProcess.java
new file mode 100644
index 00000000..9cd769c7
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/quartz/processes/ResumeAllQuartzJobsProcess.java
@@ -0,0 +1,86 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2023. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.scheduler.quartz.processes;
+
+
+import java.util.List;
+import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
+import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.WidgetHtmlLine;
+import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.NoCodeWidgetFrontendComponentMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
+import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzScheduler;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class ResumeAllQuartzJobsProcess implements BackendStep, MetaDataProducerInterface
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public QProcessMetaData produce(QInstance qInstance) throws QException
+ {
+ return new QProcessMetaData()
+ .withName(getClass().getSimpleName())
+ .withLabel("Resume All Quartz Jobs")
+ .withStepList(List.of(
+ new QBackendStepMetaData()
+ .withName("execute")
+ .withCode(new QCodeReference(getClass())),
+ new QFrontendStepMetaData()
+ .withName("results")
+ .withComponent(new NoCodeWidgetFrontendComponentMetaData()
+ .withOutput(new WidgetHtmlLine().withVelocityTemplate("All quartz jobs have been resumed")))))
+ .withIcon(new QIcon("play_circle_outline"));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
+ {
+ try
+ {
+ QuartzScheduler.getInstance().resumeAll();
+ }
+ catch(Exception e)
+ {
+ throw (new QException("Error resuming all jobs", e));
+ }
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/quartz/processes/ResumeQuartzJobsProcess.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/quartz/processes/ResumeQuartzJobsProcess.java
new file mode 100644
index 00000000..c81ccf32
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/scheduler/quartz/processes/ResumeQuartzJobsProcess.java
@@ -0,0 +1,89 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2023. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.scheduler.quartz.processes;
+
+
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
+import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.AbstractLoadStep;
+import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ExtractViaQueryStep;
+import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.NoopTransformStep;
+import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
+import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzScheduler;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class ResumeQuartzJobsProcess extends AbstractLoadStep implements MetaDataProducerInterface
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public QProcessMetaData produce(QInstance qInstance) throws QException
+ {
+ String tableName = "QUARTZ_TRIGGERS";
+
+ return StreamedETLWithFrontendProcess.processMetaDataBuilder()
+ .withName(getClass().getSimpleName())
+ .withLabel("Resume Quartz Jobs")
+ .withTableName(tableName)
+ .withSourceTable(tableName)
+ .withDestinationTable(tableName)
+ .withExtractStepClass(ExtractViaQueryStep.class)
+ .withTransformStepClass(NoopTransformStep.class)
+ .withLoadStepClass(getClass())
+ .withIcon(new QIcon("play_circle_outline"))
+ .getProcessMetaData();
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
+ {
+ try
+ {
+ QuartzScheduler instance = QuartzScheduler.getInstance();
+ for(QRecord record : runBackendStepInput.getRecords())
+ {
+ instance.resumeJob(record.getValueString("JOB_NAME"), record.getValueString("GROUP_NAME"));
+ }
+ }
+ catch(Exception e)
+ {
+ throw (new QException("Error resuming jobs", e));
+ }
+ }
+
+}
diff --git a/qqq-backend-core/src/main/resources/quartz.properties b/qqq-backend-core/src/main/resources/quartz.properties
new file mode 100644
index 00000000..3dd81314
--- /dev/null
+++ b/qqq-backend-core/src/main/resources/quartz.properties
@@ -0,0 +1,64 @@
+#
+# QQQ - Low-code Application Framework for Engineers.
+# Copyright (C) 2021-2023. Kingsrook, LLC
+# 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+# contact@kingsrook.com
+# https://github.com/Kingsrook/
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+#
+#org.quartz.scheduler.instanceName = MyScheduler
+#org.quartz.threadPool.threadCount = 3
+#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
+#
+
+
+
+#============================================================================
+# Configure Main Scheduler Properties
+#============================================================================
+org.quartz.scheduler.instanceName = MyClusteredScheduler
+org.quartz.scheduler.instanceId = AUTO
+
+#============================================================================
+# Configure ThreadPool
+#============================================================================
+org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
+org.quartz.threadPool.threadCount = 5
+org.quartz.threadPool.threadPriority = 5
+
+#============================================================================
+# Configure JobStore
+#============================================================================
+org.quartz.jobStore.misfireThreshold = 60000
+
+org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
+org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
+org.quartz.jobStore.useProperties = false
+org.quartz.jobStore.dataSource = myDS
+org.quartz.jobStore.tablePrefix = QUARTZ_
+
+org.quartz.jobStore.isClustered = true
+org.quartz.jobStore.clusterCheckinInterval = 20000
+
+#============================================================================
+# Configure Datasources
+#============================================================================
+org.quartz.dataSource.myDS.driver = com.mysql.cj.jdbc.Driver
+org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/nutrifresh_one
+org.quartz.dataSource.myDS.user = root
+org.quartz.dataSource.myDS.password = BXca6Bubxf!ECt7sua6L
+org.quartz.dataSource.myDS.maxConnections = 5
+org.quartz.dataSource.myDS.validationQuery=select 1
\ No newline at end of file