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