mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-20 14:10:44 +00:00
Compare commits
2 Commits
snapshot-f
...
snapshot-f
Author | SHA1 | Date | |
---|---|---|---|
f15277f23b | |||
d480027aeb |
@ -163,19 +163,6 @@
|
||||
<version>1.12.321</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.quartz-scheduler</groupId>
|
||||
<artifactId>quartz</artifactId>
|
||||
<version>2.3.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Many of the deps we bring in use slf4j. This dep maps slf4j to our logger, log4j -->
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-slf4j-impl</artifactId>
|
||||
<version>2.23.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Common deps for all qqq modules -->
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
|
@ -159,8 +159,7 @@ public class AsyncJobManager
|
||||
private <T extends Serializable> T runAsyncJob(String jobName, AsyncJob<T> asyncJob, UUIDAndTypeStateKey uuidAndTypeStateKey, AsyncJobStatus asyncJobStatus)
|
||||
{
|
||||
String originalThreadName = Thread.currentThread().getName();
|
||||
// Thread.currentThread().setName("Job:" + jobName + ":" + uuidAndTypeStateKey.getUuid().toString().substring(0, 8));
|
||||
Thread.currentThread().setName("Job:" + jobName);
|
||||
Thread.currentThread().setName("Job:" + jobName + ":" + uuidAndTypeStateKey.getUuid().toString().substring(0, 8));
|
||||
try
|
||||
{
|
||||
LOG.debug("Starting job " + uuidAndTypeStateKey.getUuid());
|
||||
|
@ -132,11 +132,6 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
*******************************************************************************/
|
||||
String tableName();
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
QTableAutomationDetails tableAutomationDetails();
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -148,7 +143,7 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
/*******************************************************************************
|
||||
** Wrapper for a pair of (tableName, automationStatus)
|
||||
*******************************************************************************/
|
||||
public record TableActions(String tableName, QTableAutomationDetails tableAutomationDetails, AutomationStatus status) implements TableActionsInterface
|
||||
public record TableActions(String tableName, AutomationStatus status) implements TableActionsInterface
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
@ -164,7 +159,7 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
** extended version of TableAction, for sharding use-case - adds the shard
|
||||
** details.
|
||||
*******************************************************************************/
|
||||
public record ShardedTableActions(String tableName, QTableAutomationDetails tableAutomationDetails, AutomationStatus status, String shardByFieldName, Serializable shardValue, String shardLabel) implements TableActionsInterface
|
||||
public record ShardedTableActions(String tableName, AutomationStatus status, String shardByFieldName, Serializable shardValue, String shardLabel) implements TableActionsInterface
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
@ -203,8 +198,8 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
{
|
||||
Serializable shardId = record.getValue(automationDetails.getShardIdFieldName());
|
||||
String label = record.getValueString(automationDetails.getShardLabelFieldName());
|
||||
tableActionList.add(new ShardedTableActions(table.getName(), automationDetails, AutomationStatus.PENDING_INSERT_AUTOMATIONS, automationDetails.getShardByFieldName(), shardId, label));
|
||||
tableActionList.add(new ShardedTableActions(table.getName(), automationDetails, AutomationStatus.PENDING_UPDATE_AUTOMATIONS, automationDetails.getShardByFieldName(), shardId, label));
|
||||
tableActionList.add(new ShardedTableActions(table.getName(), AutomationStatus.PENDING_INSERT_AUTOMATIONS, automationDetails.getShardByFieldName(), shardId, label));
|
||||
tableActionList.add(new ShardedTableActions(table.getName(), AutomationStatus.PENDING_UPDATE_AUTOMATIONS, automationDetails.getShardByFieldName(), shardId, label));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
@ -214,11 +209,11 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
}
|
||||
else
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// for non-sharded, we just need table name & automation status //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
tableActionList.add(new TableActions(table.getName(), automationDetails, AutomationStatus.PENDING_INSERT_AUTOMATIONS));
|
||||
tableActionList.add(new TableActions(table.getName(), automationDetails, AutomationStatus.PENDING_UPDATE_AUTOMATIONS));
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// for non-sharded, we just need tabler name & automation status //
|
||||
///////////////////////////////////////////////////////////////////
|
||||
tableActionList.add(new TableActions(table.getName(), AutomationStatus.PENDING_INSERT_AUTOMATIONS));
|
||||
tableActionList.add(new TableActions(table.getName(), AutomationStatus.PENDING_UPDATE_AUTOMATIONS));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,24 +47,12 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
** records that the delete action marked in error - the user might want to do
|
||||
** something special with them (idk, try some other way to delete them?)
|
||||
*******************************************************************************/
|
||||
public abstract class AbstractPostDeleteCustomizer implements TableCustomizerInterface
|
||||
public abstract class AbstractPostDeleteCustomizer
|
||||
{
|
||||
protected DeleteInput deleteInput;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> postDelete(DeleteInput deleteInput, List<QRecord> records) throws QException
|
||||
{
|
||||
this.deleteInput = deleteInput;
|
||||
return apply(records);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -42,24 +42,12 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
**
|
||||
** Note that the full insertInput is available as a field in this class.
|
||||
*******************************************************************************/
|
||||
public abstract class AbstractPostInsertCustomizer implements TableCustomizerInterface
|
||||
public abstract class AbstractPostInsertCustomizer
|
||||
{
|
||||
protected InsertInput insertInput;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> postInsert(InsertInput insertInput, List<QRecord> records) throws QException
|
||||
{
|
||||
this.insertInput = insertInput;
|
||||
return (apply(records));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -23,29 +23,16 @@ package com.kingsrook.qqq.backend.core.actions.customizers;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public abstract class AbstractPostQueryCustomizer implements TableCustomizerInterface
|
||||
public abstract class AbstractPostQueryCustomizer
|
||||
{
|
||||
protected QueryOrGetInputInterface input;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> postQuery(QueryOrGetInputInterface queryInput, List<QRecord> records) throws QException
|
||||
{
|
||||
input = queryInput;
|
||||
return apply(records);
|
||||
}
|
||||
protected AbstractTableActionInput input;
|
||||
|
||||
|
||||
|
||||
@ -60,7 +47,7 @@ public abstract class AbstractPostQueryCustomizer implements TableCustomizerInte
|
||||
** Getter for input
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QueryOrGetInputInterface getInput()
|
||||
public AbstractTableActionInput getInput()
|
||||
{
|
||||
return (input);
|
||||
}
|
||||
@ -71,7 +58,7 @@ public abstract class AbstractPostQueryCustomizer implements TableCustomizerInte
|
||||
** Setter for input
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setInput(QueryOrGetInputInterface input)
|
||||
public void setInput(AbstractTableActionInput input)
|
||||
{
|
||||
this.input = input;
|
||||
}
|
||||
|
@ -26,7 +26,6 @@ import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
@ -49,7 +48,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
** available (if the backend supports it) - both as a list (`getOldRecordList`)
|
||||
** and as a memoized (by this class) map of primaryKey to record (`getOldRecordMap`).
|
||||
*******************************************************************************/
|
||||
public abstract class AbstractPostUpdateCustomizer implements TableCustomizerInterface
|
||||
public abstract class AbstractPostUpdateCustomizer
|
||||
{
|
||||
protected UpdateInput updateInput;
|
||||
protected List<QRecord> oldRecordList;
|
||||
@ -58,19 +57,6 @@ public abstract class AbstractPostUpdateCustomizer implements TableCustomizerInt
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> postUpdate(UpdateInput updateInput, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
|
||||
{
|
||||
this.updateInput = updateInput;
|
||||
this.oldRecordList = oldRecordList.orElse(null);
|
||||
return apply(records);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -50,7 +50,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
** Note that the full deleteInput is available as a field in this class.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public abstract class AbstractPreDeleteCustomizer implements TableCustomizerInterface
|
||||
public abstract class AbstractPreDeleteCustomizer
|
||||
{
|
||||
protected DeleteInput deleteInput;
|
||||
|
||||
@ -58,19 +58,6 @@ public abstract class AbstractPreDeleteCustomizer implements TableCustomizerInte
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> preDelete(DeleteInput deleteInput, List<QRecord> records, boolean isPreview) throws QException
|
||||
{
|
||||
this.deleteInput = deleteInput;
|
||||
this.isPreview = isPreview;
|
||||
return apply(records);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -47,7 +47,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
**
|
||||
** Note that the full insertInput is available as a field in this class.
|
||||
*******************************************************************************/
|
||||
public abstract class AbstractPreInsertCustomizer implements TableCustomizerInterface
|
||||
public abstract class AbstractPreInsertCustomizer
|
||||
{
|
||||
protected InsertInput insertInput;
|
||||
|
||||
@ -70,30 +70,6 @@ public abstract class AbstractPreInsertCustomizer implements TableCustomizerInte
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException
|
||||
{
|
||||
this.insertInput = insertInput;
|
||||
this.isPreview = isPreview;
|
||||
return (apply(records));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public WhenToRun whenToRunPreInsert(InsertInput insertInput, boolean isPreview)
|
||||
{
|
||||
return getWhenToRun();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -26,7 +26,6 @@ import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
@ -54,7 +53,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
** available (if the backend supports it) - both as a list (`getOldRecordList`)
|
||||
** and as a memoized (by this class) map of primaryKey to record (`getOldRecordMap`).
|
||||
*******************************************************************************/
|
||||
public abstract class AbstractPreUpdateCustomizer implements TableCustomizerInterface
|
||||
public abstract class AbstractPreUpdateCustomizer
|
||||
{
|
||||
protected UpdateInput updateInput;
|
||||
protected List<QRecord> oldRecordList;
|
||||
@ -64,20 +63,6 @@ public abstract class AbstractPreUpdateCustomizer implements TableCustomizerInte
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> preUpdate(UpdateInput updateInput, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException
|
||||
{
|
||||
this.updateInput = updateInput;
|
||||
this.isPreview = isPreview;
|
||||
this.oldRecordList = oldRecordList.orElse(null);
|
||||
return apply(records);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -22,7 +22,6 @@
|
||||
package com.kingsrook.qqq.backend.core.actions.customizers;
|
||||
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
|
||||
@ -35,35 +34,42 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
|
||||
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeFunction;
|
||||
import com.kingsrook.qqq.backend.core.utils.memoization.Memoization;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility to load code for running QQQ customizers.
|
||||
**
|
||||
** TODO - redo all to go through method that memoizes class & constructor
|
||||
** lookup. That memoziation causes 1,000,000 such calls to go from ~500ms
|
||||
** to ~100ms.
|
||||
*******************************************************************************/
|
||||
public class QCodeLoader
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QCodeLoader.class);
|
||||
|
||||
private static Memoization<String, Constructor<?>> constructorMemoization = new Memoization<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static Optional<TableCustomizerInterface> getTableCustomizer(QTableMetaData table, String customizerName)
|
||||
public static <T, R> Optional<Function<T, R>> getTableCustomizerFunction(QTableMetaData table, String customizerName)
|
||||
{
|
||||
Optional<QCodeReference> codeReference = table.getCustomizer(customizerName);
|
||||
if(codeReference.isPresent())
|
||||
{
|
||||
return (Optional.ofNullable(QCodeLoader.getAdHoc(TableCustomizerInterface.class, codeReference.get())));
|
||||
return (Optional.ofNullable(QCodeLoader.getFunction(codeReference.get())));
|
||||
}
|
||||
return (Optional.empty());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static <T> Optional<T> getTableCustomizer(Class<T> expectedClass, QTableMetaData table, String customizerName)
|
||||
{
|
||||
Optional<QCodeReference> codeReference = table.getCustomizer(customizerName);
|
||||
if(codeReference.isPresent())
|
||||
{
|
||||
return (Optional.ofNullable(QCodeLoader.getAdHoc(expectedClass, codeReference.get())));
|
||||
}
|
||||
return (Optional.empty());
|
||||
}
|
||||
@ -169,21 +175,8 @@ public class QCodeLoader
|
||||
|
||||
try
|
||||
{
|
||||
Optional<Constructor<?>> constructor = constructorMemoization.getResultThrowing(codeReference.getName(), (UnsafeFunction<String, Constructor<?>, Exception>) s ->
|
||||
{
|
||||
Class<?> customizerClass = Class.forName(codeReference.getName());
|
||||
return customizerClass.getConstructor();
|
||||
});
|
||||
|
||||
if(constructor.isPresent())
|
||||
{
|
||||
return ((T) constructor.get().newInstance());
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.error("Could not get constructor for code reference", logPair("codeReference", codeReference));
|
||||
return (null);
|
||||
}
|
||||
Class<?> customizerClass = Class.forName(codeReference.getName());
|
||||
return ((T) customizerClass.getConstructor().newInstance());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
|
@ -1,202 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.customizers;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Common interface used by all (core) TableCustomizer types (e.g., post-query,
|
||||
** and {pre,post}-{insert,update,delete}.
|
||||
**
|
||||
** Note that the abstract-base classes for each action still exist, though have
|
||||
** been back-ported to be implementors of this interface. The action classes
|
||||
** will now expect this type, and call this type's methods.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public interface TableCustomizerInterface
|
||||
{
|
||||
QLogger LOG = QLogger.getLogger(TableCustomizerInterface.class);
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** custom actions to run after a query (or get!) takes place.
|
||||
**
|
||||
*******************************************************************************/
|
||||
default List<QRecord> postQuery(QueryOrGetInputInterface queryInput, List<QRecord> records) throws QException
|
||||
{
|
||||
LOG.info("A default implementation of postQuery is running... Probably not expected!", logPair("tableName", queryInput.getTableName()));
|
||||
return (records);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** custom actions before an insert takes place.
|
||||
**
|
||||
** It's important for implementations to be aware of the isPreview field, which
|
||||
** is set to true when the code is running to give users advice, e.g., on a review
|
||||
** screen - vs. being false when the action is ACTUALLY happening. So, if you're doing
|
||||
** things like storing data, you don't want to do that if isPreview is true!!
|
||||
**
|
||||
** General implementation would be, to iterate over the records (the inputs to
|
||||
** the insert action), and look at their values:
|
||||
** - possibly adding Errors (`addError`) or Warnings (`addWarning`) to the records
|
||||
** - possibly manipulating values (`setValue`)
|
||||
** - possibly throwing an exception - if you really don't want the insert operation to continue.
|
||||
** - doing "whatever else" you may want to do.
|
||||
** - returning the list of records (can be the input list) that you want to go on to the backend implementation class.
|
||||
*******************************************************************************/
|
||||
default List<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException
|
||||
{
|
||||
LOG.info("A default implementation of preInsert is running... Probably not expected!", logPair("tableName", insertInput.getTableName()));
|
||||
return (records);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
default AbstractPreInsertCustomizer.WhenToRun whenToRunPreInsert(InsertInput insertInput, boolean isPreview)
|
||||
{
|
||||
return (AbstractPreInsertCustomizer.WhenToRun.AFTER_ALL_VALIDATIONS);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** custom actions after an insert takes place.
|
||||
**
|
||||
** General implementation would be, to iterate over the records (the outputs of
|
||||
** the insert action), and look at their values:
|
||||
** - possibly adding Errors (`addError`) or Warnings (`addWarning`) to the records
|
||||
** - possibly throwing an exception - though doing so won't stop the update, and instead
|
||||
** will just set a warning on all of the updated records...
|
||||
** - doing "whatever else" you may want to do.
|
||||
** - returning the list of records (can be the input list) that you want to go back to the caller.
|
||||
*******************************************************************************/
|
||||
default List<QRecord> postInsert(InsertInput insertInput, List<QRecord> records) throws QException
|
||||
{
|
||||
LOG.info("A default implementation of postInsert is running... Probably not expected!", logPair("tableName", insertInput.getTableName()));
|
||||
return (records);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** custom actions before an update takes place.
|
||||
**
|
||||
** It's important for implementations to be aware of the isPreview field, which
|
||||
** is set to true when the code is running to give users advice, e.g., on a review
|
||||
** screen - vs. being false when the action is ACTUALLY happening. So, if you're doing
|
||||
** things like storing data, you don't want to do that if isPreview is true!!
|
||||
**
|
||||
** General implementation would be, to iterate over the records (the inputs to
|
||||
** the update action), and look at their values:
|
||||
** - possibly adding Errors (`addError`) or Warnings (`addWarning`) to the records
|
||||
** - possibly manipulating values (`setValue`)
|
||||
** - possibly throwing an exception - if you really don't want the update operation to continue.
|
||||
** - doing "whatever else" you may want to do.
|
||||
** - returning the list of records (can be the input list) that you want to go on to the backend implementation class.
|
||||
**
|
||||
** Note, "old records" (e.g., with values freshly fetched from the backend) will be
|
||||
** available (if the backend supports it)
|
||||
*******************************************************************************/
|
||||
default List<QRecord> preUpdate(UpdateInput updateInput, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException
|
||||
{
|
||||
LOG.info("A default implementation of preUpdate is running... Probably not expected!", logPair("tableName", updateInput.getTableName()));
|
||||
return (records);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** custom actions after an update takes place.
|
||||
**
|
||||
** General implementation would be, to iterate over the records (the outputs of
|
||||
** the update action), and look at their values:
|
||||
** - possibly adding Errors (`addError`) or Warnings (`addWarning`) to the records?
|
||||
** - possibly throwing an exception - though doing so won't stop the update, and instead
|
||||
** will just set a warning on all of the updated records...
|
||||
** - doing "whatever else" you may want to do.
|
||||
** - returning the list of records (can be the input list) that you want to go back to the caller.
|
||||
**
|
||||
** Note, "old records" (e.g., with values freshly fetched from the backend) will be
|
||||
** available (if the backend supports it).
|
||||
*******************************************************************************/
|
||||
default List<QRecord> postUpdate(UpdateInput updateInput, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
|
||||
{
|
||||
LOG.info("A default implementation of postUpdate is running... Probably not expected!", logPair("tableName", updateInput.getTableName()));
|
||||
return (records);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Custom actions before a delete takes place.
|
||||
**
|
||||
** It's important for implementations to be aware of the isPreview param, which
|
||||
** is set to true when the code is running to give users advice, e.g., on a review
|
||||
** screen - vs. being false when the action is ACTUALLY happening. So, if you're doing
|
||||
** things like storing data, you don't want to do that if isPreview is true!!
|
||||
**
|
||||
** General implementation would be, to iterate over the records (which the DeleteAction
|
||||
** would look up based on the inputs to the delete action), and look at their values:
|
||||
** - possibly adding Errors (`addError`) or Warnings (`addWarning`) to the records
|
||||
** - possibly throwing an exception - if you really don't want the delete operation to continue.
|
||||
** - doing "whatever else" you may want to do.
|
||||
** - returning the list of records (can be the input list) - this is how errors
|
||||
** and warnings are propagated to the DeleteAction. Note that any records with
|
||||
** an error will NOT proceed to the backend's delete interface - but those with
|
||||
** warnings will.
|
||||
*******************************************************************************/
|
||||
default List<QRecord> preDelete(DeleteInput deleteInput, List<QRecord> records, boolean isPreview) throws QException
|
||||
{
|
||||
LOG.info("A default implementation of preDelete is running... Probably not expected!", logPair("tableName", deleteInput.getTableName()));
|
||||
return (records);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Custom actions after a delete takes place.
|
||||
**
|
||||
** General implementation would be, to iterate over the records (ones which didn't
|
||||
** have a delete error), and look at their values:
|
||||
** - possibly adding Errors (`addError`) or Warnings (`addWarning`) to the records?
|
||||
** - possibly throwing an exception - though doing so won't stop the delete, and instead
|
||||
** will just set a warning on all of the deleted records...
|
||||
** - doing "whatever else" you may want to do.
|
||||
** - returning the list of records (can be the input list) that you want to go back
|
||||
** to the caller - this is how errors and warnings are propagated .
|
||||
*******************************************************************************/
|
||||
default List<QRecord> postDelete(DeleteInput deleteInput, List<QRecord> records) throws QException
|
||||
{
|
||||
LOG.info("A default implementation of postDelete is running... Probably not expected!", logPair("tableName", deleteInput.getTableName()));
|
||||
return (records);
|
||||
}
|
||||
|
||||
}
|
@ -29,13 +29,13 @@ package com.kingsrook.qqq.backend.core.actions.customizers;
|
||||
*******************************************************************************/
|
||||
public enum TableCustomizers
|
||||
{
|
||||
POST_QUERY_RECORD("postQueryRecord", TableCustomizerInterface.class),
|
||||
PRE_INSERT_RECORD("preInsertRecord", TableCustomizerInterface.class),
|
||||
POST_INSERT_RECORD("postInsertRecord", TableCustomizerInterface.class),
|
||||
PRE_UPDATE_RECORD("preUpdateRecord", TableCustomizerInterface.class),
|
||||
POST_UPDATE_RECORD("postUpdateRecord", TableCustomizerInterface.class),
|
||||
PRE_DELETE_RECORD("preDeleteRecord", TableCustomizerInterface.class),
|
||||
POST_DELETE_RECORD("postDeleteRecord", TableCustomizerInterface.class);
|
||||
POST_QUERY_RECORD("postQueryRecord", AbstractPostQueryCustomizer.class),
|
||||
PRE_INSERT_RECORD("preInsertRecord", AbstractPreInsertCustomizer.class),
|
||||
POST_INSERT_RECORD("postInsertRecord", AbstractPostInsertCustomizer.class),
|
||||
PRE_UPDATE_RECORD("preUpdateRecord", AbstractPreUpdateCustomizer.class),
|
||||
POST_UPDATE_RECORD("postUpdateRecord", AbstractPostUpdateCustomizer.class),
|
||||
PRE_DELETE_RECORD("preDeleteRecord", AbstractPreDeleteCustomizer.class),
|
||||
POST_DELETE_RECORD("postDeleteRecord", AbstractPostDeleteCustomizer.class);
|
||||
|
||||
|
||||
private final String role;
|
||||
|
@ -1,202 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.dashboard.widgets;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.AggregateAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.Aggregate;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateResult;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.GroupBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.QFilterOrderByAggregate;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.QFilterOrderByGroupBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.TableData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Generic widget that does an aggregate query, and presents its results
|
||||
** as a table, using group-by values as both row & column labels.
|
||||
*******************************************************************************/
|
||||
public class Aggregate2DTableWidgetRenderer extends AbstractWidgetRenderer
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(Aggregate2DTableWidgetRenderer.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public RenderWidgetOutput render(RenderWidgetInput input) throws QException
|
||||
{
|
||||
Map<String, Serializable> values = input.getWidgetMetaData().getDefaultValues();
|
||||
|
||||
String tableName = ValueUtils.getValueAsString(values.get("tableName"));
|
||||
String valueField = ValueUtils.getValueAsString(values.get("valueField"));
|
||||
String rowField = ValueUtils.getValueAsString(values.get("rowField"));
|
||||
String columnField = ValueUtils.getValueAsString(values.get("columnField"));
|
||||
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||
|
||||
AggregateInput aggregateInput = new AggregateInput();
|
||||
aggregateInput.setTableName(tableName);
|
||||
|
||||
// todo - allow input of "list of columns" (e.g., in case some miss sometimes, or as a version of filter)
|
||||
// todo - max rows, max cols?
|
||||
|
||||
// todo - from input map
|
||||
QQueryFilter filter = new QQueryFilter();
|
||||
aggregateInput.setFilter(filter);
|
||||
|
||||
Aggregate aggregate = new Aggregate(valueField, AggregateOperator.COUNT);
|
||||
aggregateInput.withAggregate(aggregate);
|
||||
|
||||
GroupBy rowGroupBy = new GroupBy(table.getField(rowField));
|
||||
GroupBy columnGroupBy = new GroupBy(table.getField(columnField));
|
||||
aggregateInput.withGroupBy(rowGroupBy);
|
||||
aggregateInput.withGroupBy(columnGroupBy);
|
||||
|
||||
String orderBys = ValueUtils.getValueAsString(values.get("orderBys"));
|
||||
if(StringUtils.hasContent(orderBys))
|
||||
{
|
||||
for(String orderBy : orderBys.split(","))
|
||||
{
|
||||
switch(orderBy)
|
||||
{
|
||||
case "row" -> filter.addOrderBy(new QFilterOrderByGroupBy(rowGroupBy));
|
||||
case "column" -> filter.addOrderBy(new QFilterOrderByGroupBy(columnGroupBy));
|
||||
case "value" -> filter.addOrderBy(new QFilterOrderByAggregate(aggregate));
|
||||
default -> LOG.warn("Unrecognized orderBy: " + orderBy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AggregateOutput aggregateOutput = new AggregateAction().execute(aggregateInput);
|
||||
|
||||
Map<Serializable, Map<Serializable, Serializable>> data = new LinkedHashMap<>();
|
||||
Set<Serializable> columnsSet = new LinkedHashSet<>();
|
||||
|
||||
for(AggregateResult result : aggregateOutput.getResults())
|
||||
{
|
||||
Serializable column = result.getGroupByValue(columnGroupBy);
|
||||
Serializable row = result.getGroupByValue(rowGroupBy);
|
||||
Serializable value = result.getAggregateValue(aggregate);
|
||||
|
||||
Map<Serializable, Serializable> rowMap = data.computeIfAbsent(row, (k) -> new LinkedHashMap<>());
|
||||
rowMap.put(column, value);
|
||||
columnsSet.add(column);
|
||||
}
|
||||
|
||||
// todo - possible values from rows, cols
|
||||
|
||||
////////////////////////////////////
|
||||
// setup datastructures for table //
|
||||
////////////////////////////////////
|
||||
List<Map<String, Object>> tableRows = new ArrayList<>();
|
||||
List<TableData.Column> tableColumns = new ArrayList<>();
|
||||
tableColumns.add(new TableData.Column("default", table.getField(rowField).getLabel(), "_row", "2fr", "left"));
|
||||
|
||||
for(Serializable column : columnsSet)
|
||||
{
|
||||
tableColumns.add(new TableData.Column("default", String.valueOf(column) /* todo display value */, String.valueOf(column), "1fr", "right"));
|
||||
}
|
||||
|
||||
tableColumns.add(new TableData.Column("default", "Total", "_total", "1fr", "right"));
|
||||
|
||||
TableData tableData = new TableData(null, tableColumns, tableRows)
|
||||
.withRowsPerPage(100)
|
||||
.withFixedStickyLastRow(false)
|
||||
.withHidePaginationDropdown(true);
|
||||
|
||||
Map<Serializable, Integer> columnSums = new HashMap<>();
|
||||
int grandTotal = 0;
|
||||
for(Map.Entry<Serializable, Map<Serializable, Serializable>> rowEntry : data.entrySet())
|
||||
{
|
||||
Map<String, Object> rowMap = new HashMap<>();
|
||||
tableRows.add(rowMap);
|
||||
|
||||
rowMap.put("_row", rowEntry.getKey() /* todo display value */);
|
||||
int rowTotal = 0;
|
||||
for(Serializable column : columnsSet)
|
||||
{
|
||||
Serializable value = rowEntry.getValue().get(column);
|
||||
if(value == null)
|
||||
{
|
||||
value = 0; // todo?
|
||||
}
|
||||
|
||||
Integer valueAsInteger = Objects.requireNonNullElse(ValueUtils.getValueAsInteger(value), 0);
|
||||
rowTotal += valueAsInteger;
|
||||
columnSums.putIfAbsent(column, 0);
|
||||
columnSums.put(column, columnSums.get(column) + valueAsInteger);
|
||||
|
||||
rowMap.put(String.valueOf(column), value); // todo format commas?
|
||||
}
|
||||
|
||||
rowMap.put("_total", rowTotal);
|
||||
grandTotal += rowTotal;
|
||||
}
|
||||
|
||||
///////////////
|
||||
// total row //
|
||||
///////////////
|
||||
Map<String, Object> totalRowMap = new HashMap<>();
|
||||
tableRows.add(totalRowMap);
|
||||
|
||||
totalRowMap.put("_row", "Total");
|
||||
int rowTotal = 0;
|
||||
for(Serializable column : columnsSet)
|
||||
{
|
||||
Serializable value = columnSums.get(column);
|
||||
if(value == null)
|
||||
{
|
||||
value = 0; // todo?
|
||||
}
|
||||
|
||||
totalRowMap.put(String.valueOf(column), value); // todo format commas?
|
||||
}
|
||||
|
||||
totalRowMap.put("_total", grandTotal);
|
||||
|
||||
return (new RenderWidgetOutput(tableData));
|
||||
}
|
||||
|
||||
}
|
@ -137,7 +137,7 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||
*******************************************************************************/
|
||||
public Builder withCanAddChildRecord(boolean b)
|
||||
{
|
||||
widgetMetaData.withDefaultValue("canAddChildRecord", b);
|
||||
widgetMetaData.withDefaultValue("canAddChildRecord", true);
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -151,17 +151,6 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||
widgetMetaData.withDefaultValue("disabledFieldsForNewChildRecords", new HashSet<>(disabledFieldsForNewChildRecords));
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Builder withManageAssociationName(String manageAssociationName)
|
||||
{
|
||||
widgetMetaData.withDefaultValue("manageAssociationName", manageAssociationName);
|
||||
return (this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -189,60 +178,52 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||
maxRows = ValueUtils.getValueAsInteger(input.getWidgetMetaData().getDefaultValues().containsKey("maxRows"));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// fetch the record that we're getting children for. e.g., the left-side of the join, with the input id //
|
||||
// but - only try this if we were given an id. note, this widget could be called for on an INSERT screen, where we don't have a record yet //
|
||||
// but we still want to be able to return all the other data in here that otherwise comes from the widget meta data, join, etc. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
int totalRows = 0;
|
||||
QRecord primaryRecord = null;
|
||||
QQueryFilter filter = null;
|
||||
QueryOutput queryOutput = new QueryOutput(new QueryInput());
|
||||
if(StringUtils.hasContent(id))
|
||||
////////////////////////////////////////////////////////
|
||||
// fetch the record that we're getting children for. //
|
||||
// e.g., the left-side of the join, with the input id //
|
||||
////////////////////////////////////////////////////////
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(join.getLeftTable());
|
||||
getInput.setPrimaryKey(id);
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
QRecord record = getOutput.getRecord();
|
||||
|
||||
if(record == null)
|
||||
{
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(join.getLeftTable());
|
||||
getInput.setPrimaryKey(id);
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
primaryRecord = getOutput.getRecord();
|
||||
throw (new QNotFoundException("Could not find " + (leftTable == null ? "" : leftTable.getLabel()) + " with primary key " + id));
|
||||
}
|
||||
|
||||
if(primaryRecord == null)
|
||||
{
|
||||
throw (new QNotFoundException("Could not find " + (leftTable == null ? "" : leftTable.getLabel()) + " with primary key " + id));
|
||||
}
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// set up the query - for the table on the right side of the join //
|
||||
////////////////////////////////////////////////////////////////////
|
||||
QQueryFilter filter = new QQueryFilter();
|
||||
for(JoinOn joinOn : join.getJoinOns())
|
||||
{
|
||||
filter.addCriteria(new QFilterCriteria(joinOn.getRightField(), QCriteriaOperator.EQUALS, List.of(record.getValue(joinOn.getLeftField()))));
|
||||
}
|
||||
filter.setOrderBys(join.getOrderBys());
|
||||
filter.setLimit(maxRows);
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// set up the query - for the table on the right side of the join //
|
||||
////////////////////////////////////////////////////////////////////
|
||||
filter = new QQueryFilter();
|
||||
for(JoinOn joinOn : join.getJoinOns())
|
||||
{
|
||||
filter.addCriteria(new QFilterCriteria(joinOn.getRightField(), QCriteriaOperator.EQUALS, List.of(primaryRecord.getValue(joinOn.getLeftField()))));
|
||||
}
|
||||
filter.setOrderBys(join.getOrderBys());
|
||||
filter.setLimit(maxRows);
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(join.getRightTable());
|
||||
queryInput.setShouldTranslatePossibleValues(true);
|
||||
queryInput.setShouldGenerateDisplayValues(true);
|
||||
queryInput.setFilter(filter);
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(join.getRightTable());
|
||||
queryInput.setShouldTranslatePossibleValues(true);
|
||||
queryInput.setShouldGenerateDisplayValues(true);
|
||||
queryInput.setFilter(filter);
|
||||
queryOutput = new QueryAction().execute(queryInput);
|
||||
QValueFormatter.setBlobValuesToDownloadUrls(rightTable, queryOutput.getRecords());
|
||||
|
||||
QValueFormatter.setBlobValuesToDownloadUrls(rightTable, queryOutput.getRecords());
|
||||
|
||||
totalRows = queryOutput.getRecords().size();
|
||||
if(maxRows != null && (queryOutput.getRecords().size() == maxRows))
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the input said to only do some max, and the # of results we got is that max, //
|
||||
// then do a count query, for displaying 1-n of <count> //
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
CountInput countInput = new CountInput();
|
||||
countInput.setTableName(join.getRightTable());
|
||||
countInput.setFilter(filter);
|
||||
totalRows = new CountAction().execute(countInput).getCount();
|
||||
}
|
||||
int totalRows = queryOutput.getRecords().size();
|
||||
if(maxRows != null && (queryOutput.getRecords().size() == maxRows))
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the input said to only do some max, and the # of results we got is that max, //
|
||||
// then do a count query, for displaying 1-n of <count> //
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
CountInput countInput = new CountInput();
|
||||
countInput.setTableName(join.getRightTable());
|
||||
countInput.setFilter(filter);
|
||||
totalRows = new CountAction().execute(countInput).getCount();
|
||||
}
|
||||
|
||||
String tablePath = input.getInstance().getTablePath(rightTable.getName());
|
||||
@ -258,14 +239,10 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||
// new child records must have values from the join-ons //
|
||||
//////////////////////////////////////////////////////////
|
||||
Map<String, Serializable> defaultValuesForNewChildRecords = new HashMap<>();
|
||||
if(primaryRecord != null)
|
||||
for(JoinOn joinOn : join.getJoinOns())
|
||||
{
|
||||
for(JoinOn joinOn : join.getJoinOns())
|
||||
{
|
||||
defaultValuesForNewChildRecords.put(joinOn.getRightField(), primaryRecord.getValue(joinOn.getLeftField()));
|
||||
}
|
||||
defaultValuesForNewChildRecords.put(joinOn.getRightField(), record.getValue(joinOn.getLeftField()));
|
||||
}
|
||||
|
||||
widgetData.setDefaultValuesForNewChildRecords(defaultValuesForNewChildRecords);
|
||||
|
||||
Map<String, Serializable> widgetValues = input.getWidgetMetaData().getDefaultValues();
|
||||
@ -273,22 +250,6 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||
{
|
||||
widgetData.setDisabledFieldsForNewChildRecords((Set<String>) widgetValues.get("disabledFieldsForNewChildRecords"));
|
||||
}
|
||||
else
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if there are no disabled fields specified - then normally any fields w/ a default value get implicitly disabled //
|
||||
// but - if we didn't look-up the primary record, then we'll want to explicit disable fields from joins //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(primaryRecord == null)
|
||||
{
|
||||
Set<String> implicitlyDisabledFields = new HashSet<>();
|
||||
widgetData.setDisabledFieldsForNewChildRecords(implicitlyDisabledFields);
|
||||
for(JoinOn joinOn : join.getJoinOns())
|
||||
{
|
||||
implicitlyDisabledFields.add(joinOn.getRightField());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (new RenderWidgetOutput(widgetData));
|
||||
|
@ -34,18 +34,15 @@ import com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.DenyBehavior;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithName;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithPermissionRules;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.PermissionLevel;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
@ -336,25 +333,9 @@ public class PermissionsHelper
|
||||
QPermissionRules rules = getEffectivePermissionRules(tableMetaData, instance);
|
||||
String baseName = getEffectivePermissionBaseName(rules, tableMetaData.getName());
|
||||
|
||||
QBackendMetaData backend = instance.getBackend(tableMetaData.getBackendName());
|
||||
if(tableMetaData.isCapabilityEnabled(backend, Capability.TABLE_INSERT))
|
||||
for(TablePermissionSubType permissionSubType : TablePermissionSubType.values())
|
||||
{
|
||||
addEffectiveAvailablePermission(rules, TablePermissionSubType.INSERT, rs, baseName, tableMetaData, "Table");
|
||||
}
|
||||
|
||||
if(tableMetaData.isCapabilityEnabled(backend, Capability.TABLE_UPDATE))
|
||||
{
|
||||
addEffectiveAvailablePermission(rules, TablePermissionSubType.EDIT, rs, baseName, tableMetaData, "Table");
|
||||
}
|
||||
|
||||
if(tableMetaData.isCapabilityEnabled(backend, Capability.TABLE_DELETE))
|
||||
{
|
||||
addEffectiveAvailablePermission(rules, TablePermissionSubType.DELETE, rs, baseName, tableMetaData, "Table");
|
||||
}
|
||||
|
||||
if(tableMetaData.isCapabilityEnabled(backend, Capability.TABLE_QUERY) || tableMetaData.isCapabilityEnabled(backend, Capability.TABLE_GET))
|
||||
{
|
||||
addEffectiveAvailablePermission(rules, TablePermissionSubType.READ, rs, baseName, tableMetaData, "Table");
|
||||
addEffectiveAvailablePermission(rules, permissionSubType, rs, baseName, tableMetaData, "Table");
|
||||
}
|
||||
}
|
||||
|
||||
@ -388,10 +369,7 @@ public class PermissionsHelper
|
||||
{
|
||||
QPermissionRules rules = getEffectivePermissionRules(widgetMetaData, instance);
|
||||
String baseName = getEffectivePermissionBaseName(rules, widgetMetaData.getName());
|
||||
if(!rules.getLevel().equals(PermissionLevel.NOT_PROTECTED))
|
||||
{
|
||||
addEffectiveAvailablePermission(rules, PrivatePermissionSubType.HAS_ACCESS, rs, baseName, widgetMetaData, "Widget");
|
||||
}
|
||||
addEffectiveAvailablePermission(rules, PrivatePermissionSubType.HAS_ACCESS, rs, baseName, widgetMetaData, "Widget");
|
||||
}
|
||||
|
||||
return (rs);
|
||||
|
@ -497,10 +497,10 @@ public class RunProcessAction
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if backend specifies that it uses variants, look for that data in the session and append to our basepull key //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(process.getSchedule() != null && process.getVariantBackend() != null)
|
||||
if(process.getSchedule() != null && process.getSchedule().getVariantBackend() != null)
|
||||
{
|
||||
QSession session = QContext.getQSession();
|
||||
QBackendMetaData backendMetaData = QContext.getQInstance().getBackend(process.getVariantBackend());
|
||||
QBackendMetaData backendMetaData = QContext.getQInstance().getBackend(process.getSchedule().getVariantBackend());
|
||||
if(session.getBackendVariants() == null || !session.getBackendVariants().containsKey(backendMetaData.getVariantOptionsTableTypeValue()))
|
||||
{
|
||||
LOG.info("Could not find Backend Variant information for Backend '" + backendMetaData.getName() + "'");
|
||||
|
@ -73,7 +73,6 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.aggregates.AggregatesInterface;
|
||||
import com.kingsrook.qqq.backend.core.utils.aggregates.BigDecimalAggregates;
|
||||
import com.kingsrook.qqq.backend.core.utils.aggregates.IntegerAggregates;
|
||||
import com.kingsrook.qqq.backend.core.utils.aggregates.LongAggregates;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -554,12 +553,6 @@ public class GenerateReportAction
|
||||
AggregatesInterface<Integer> fieldAggregates = (AggregatesInterface<Integer>) aggregatesMap.computeIfAbsent(field.getName(), (name) -> new IntegerAggregates());
|
||||
fieldAggregates.add(record.getValueInteger(field.getName()));
|
||||
}
|
||||
else if(field.getType().equals(QFieldType.LONG))
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
AggregatesInterface<Long> fieldAggregates = (AggregatesInterface<Long>) aggregatesMap.computeIfAbsent(field.getName(), (name) -> new LongAggregates());
|
||||
fieldAggregates.add(record.getValueLong(field.getName()));
|
||||
}
|
||||
else if(field.getType().equals(QFieldType.DECIMAL))
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -35,8 +35,9 @@ import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.audits.DMLAuditAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostDeleteCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreDeleteCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.ValidateRecordSecurityLockHelper;
|
||||
@ -249,7 +250,7 @@ public class DeleteAction
|
||||
//////////////////////////////////////////////////////////////
|
||||
// finally, run the post-delete customizer, if there is one //
|
||||
//////////////////////////////////////////////////////////////
|
||||
Optional<TableCustomizerInterface> postDeleteCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.POST_DELETE_RECORD.getRole());
|
||||
Optional<AbstractPostDeleteCustomizer> postDeleteCustomizer = QCodeLoader.getTableCustomizer(AbstractPostDeleteCustomizer.class, table, TableCustomizers.POST_DELETE_RECORD.getRole());
|
||||
if(postDeleteCustomizer.isPresent() && oldRecordList.isPresent())
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
@ -259,7 +260,8 @@ public class DeleteAction
|
||||
|
||||
try
|
||||
{
|
||||
List<QRecord> postCustomizerResult = postDeleteCustomizer.get().postDelete(deleteInput, recordsForCustomizer);
|
||||
postDeleteCustomizer.get().setDeleteInput(deleteInput);
|
||||
List<QRecord> postCustomizerResult = postDeleteCustomizer.get().apply(recordsForCustomizer);
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// check if any records got errors in the customizer //
|
||||
@ -325,11 +327,13 @@ public class DeleteAction
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// after all validations, run the pre-delete customizer, if there is one //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
Optional<TableCustomizerInterface> preDeleteCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.PRE_DELETE_RECORD.getRole());
|
||||
Optional<AbstractPreDeleteCustomizer> preDeleteCustomizer = QCodeLoader.getTableCustomizer(AbstractPreDeleteCustomizer.class, table, TableCustomizers.PRE_DELETE_RECORD.getRole());
|
||||
List<QRecord> customizerResult = oldRecordList.get();
|
||||
if(preDeleteCustomizer.isPresent())
|
||||
{
|
||||
customizerResult = preDeleteCustomizer.get().preDelete(deleteInput, oldRecordList.get(), isPreview);
|
||||
preDeleteCustomizer.get().setDeleteInput(deleteInput);
|
||||
preDeleteCustomizer.get().setIsPreview(isPreview);
|
||||
customizerResult = preDeleteCustomizer.get().apply(oldRecordList.get());
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
@ -27,8 +27,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostQueryCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.GetInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.GetActionCacheHelper;
|
||||
@ -49,7 +49,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -58,7 +57,7 @@ import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
|
||||
*******************************************************************************/
|
||||
public class GetAction
|
||||
{
|
||||
private Optional<TableCustomizerInterface> postGetRecordCustomizer;
|
||||
private Optional<AbstractPostQueryCustomizer> postGetRecordCustomizer;
|
||||
|
||||
private GetInput getInput;
|
||||
private QPossibleValueTranslator qPossibleValueTranslator;
|
||||
@ -88,7 +87,7 @@ public class GetAction
|
||||
throw (new QException("Requested to Get a record from an unrecognized table: " + getInput.getTableName()));
|
||||
}
|
||||
|
||||
postGetRecordCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.POST_QUERY_RECORD.getRole());
|
||||
postGetRecordCustomizer = QCodeLoader.getTableCustomizer(AbstractPostQueryCustomizer.class, table, TableCustomizers.POST_QUERY_RECORD.getRole());
|
||||
this.getInput = getInput;
|
||||
|
||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||
@ -108,11 +107,9 @@ public class GetAction
|
||||
}
|
||||
|
||||
GetOutput getOutput;
|
||||
boolean usingDefaultGetInterface = false;
|
||||
if(getInterface == null)
|
||||
{
|
||||
getInterface = new DefaultGetInterface();
|
||||
usingDefaultGetInterface = true;
|
||||
}
|
||||
|
||||
getInterface.validateInput(getInput);
|
||||
@ -126,11 +123,10 @@ public class GetAction
|
||||
new GetActionCacheHelper().handleCaching(getInput, getOutput);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the record is found, perform post-actions on it //
|
||||
// unless the defaultGetInterface was used - as it just does a query, and the query will do the post-actions. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(getOutput.getRecord() != null && !usingDefaultGetInterface)
|
||||
////////////////////////////////////////////////////////
|
||||
// if the record is found, perform post-actions on it //
|
||||
////////////////////////////////////////////////////////
|
||||
if(getOutput.getRecord() != null)
|
||||
{
|
||||
getOutput.setRecord(postRecordActions(getOutput.getRecord()));
|
||||
}
|
||||
@ -206,7 +202,7 @@ public class GetAction
|
||||
}
|
||||
else
|
||||
{
|
||||
throw (new QException("Unable to get " + ObjectUtils.tryElse(() -> queryInput.getTable().getLabel(), queryInput.getTableName()) + ". Missing required input."));
|
||||
throw (new QException("No primaryKey or uniqueKey was passed to Get"));
|
||||
}
|
||||
|
||||
queryInput.setFilter(filter);
|
||||
@ -220,12 +216,12 @@ public class GetAction
|
||||
** Run the necessary actions on a record. This may include setting display values,
|
||||
** translating possible values, and running post-record customizations.
|
||||
*******************************************************************************/
|
||||
public QRecord postRecordActions(QRecord record) throws QException
|
||||
public QRecord postRecordActions(QRecord record)
|
||||
{
|
||||
QRecord returnRecord = record;
|
||||
if(this.postGetRecordCustomizer.isPresent())
|
||||
{
|
||||
returnRecord = postGetRecordCustomizer.get().postQuery(getInput, List.of(record)).get(0);
|
||||
returnRecord = postGetRecordCustomizer.get().apply(List.of(record)).get(0);
|
||||
}
|
||||
|
||||
if(getInput.getShouldTranslatePossibleValues())
|
||||
|
@ -28,7 +28,6 @@ import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
@ -38,9 +37,9 @@ import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.actions.audits.DMLAuditAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationStatusUpdater;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostInsertCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreInsertCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.InsertInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.UniqueKeyHelper;
|
||||
@ -169,12 +168,13 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
//////////////////////////////////////////////////////////////
|
||||
// finally, run the post-insert customizer, if there is one //
|
||||
//////////////////////////////////////////////////////////////
|
||||
Optional<TableCustomizerInterface> postInsertCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.POST_INSERT_RECORD.getRole());
|
||||
Optional<AbstractPostInsertCustomizer> postInsertCustomizer = QCodeLoader.getTableCustomizer(AbstractPostInsertCustomizer.class, table, TableCustomizers.POST_INSERT_RECORD.getRole());
|
||||
if(postInsertCustomizer.isPresent())
|
||||
{
|
||||
try
|
||||
{
|
||||
insertOutput.setRecords(postInsertCustomizer.get().postInsert(insertInput, insertOutput.getRecords()));
|
||||
postInsertCustomizer.get().setInsertInput(insertInput);
|
||||
insertOutput.setRecords(postInsertCustomizer.get().apply(insertOutput.getRecords()));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@ -232,29 +232,31 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
// load the pre-insert customizer and set it up, if there is one //
|
||||
// then we'll run it based on its WhenToRun value //
|
||||
///////////////////////////////////////////////////////////////////
|
||||
Optional<TableCustomizerInterface> preInsertCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.PRE_INSERT_RECORD.getRole());
|
||||
Optional<AbstractPreInsertCustomizer> preInsertCustomizer = QCodeLoader.getTableCustomizer(AbstractPreInsertCustomizer.class, table, TableCustomizers.PRE_INSERT_RECORD.getRole());
|
||||
if(preInsertCustomizer.isPresent())
|
||||
{
|
||||
runPreInsertCustomizerIfItIsTime(insertInput, isPreview, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_ALL_VALIDATIONS);
|
||||
preInsertCustomizer.get().setInsertInput(insertInput);
|
||||
preInsertCustomizer.get().setIsPreview(isPreview);
|
||||
runPreInsertCustomizerIfItIsTime(insertInput, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_ALL_VALIDATIONS);
|
||||
}
|
||||
|
||||
setDefaultValuesInRecords(table, insertInput.getRecords());
|
||||
|
||||
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.INSERT, insertInput.getInstance(), table, insertInput.getRecords(), null);
|
||||
|
||||
runPreInsertCustomizerIfItIsTime(insertInput, isPreview, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_UNIQUE_KEY_CHECKS);
|
||||
runPreInsertCustomizerIfItIsTime(insertInput, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_UNIQUE_KEY_CHECKS);
|
||||
setErrorsIfUniqueKeyErrors(insertInput, table);
|
||||
|
||||
runPreInsertCustomizerIfItIsTime(insertInput, isPreview, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_REQUIRED_FIELD_CHECKS);
|
||||
runPreInsertCustomizerIfItIsTime(insertInput, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_REQUIRED_FIELD_CHECKS);
|
||||
if(insertInput.getInputSource().shouldValidateRequiredFields())
|
||||
{
|
||||
validateRequiredFields(insertInput);
|
||||
}
|
||||
|
||||
runPreInsertCustomizerIfItIsTime(insertInput, isPreview, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_SECURITY_CHECKS);
|
||||
runPreInsertCustomizerIfItIsTime(insertInput, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_SECURITY_CHECKS);
|
||||
ValidateRecordSecurityLockHelper.validateSecurityFields(insertInput.getTable(), insertInput.getRecords(), ValidateRecordSecurityLockHelper.Action.INSERT);
|
||||
|
||||
runPreInsertCustomizerIfItIsTime(insertInput, isPreview, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.AFTER_ALL_VALIDATIONS);
|
||||
runPreInsertCustomizerIfItIsTime(insertInput, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.AFTER_ALL_VALIDATIONS);
|
||||
}
|
||||
|
||||
|
||||
@ -288,13 +290,13 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void runPreInsertCustomizerIfItIsTime(InsertInput insertInput, boolean isPreview, Optional<TableCustomizerInterface> preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun whenToRun) throws QException
|
||||
private void runPreInsertCustomizerIfItIsTime(InsertInput insertInput, Optional<AbstractPreInsertCustomizer> preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun whenToRun) throws QException
|
||||
{
|
||||
if(preInsertCustomizer.isPresent())
|
||||
{
|
||||
if(whenToRun.equals(preInsertCustomizer.get().whenToRunPreInsert(insertInput, isPreview)))
|
||||
if(whenToRun.equals(preInsertCustomizer.get().getWhenToRun()))
|
||||
{
|
||||
insertInput.setRecords(preInsertCustomizer.get().preInsert(insertInput, insertInput.getRecords(), isPreview));
|
||||
insertInput.setRecords(preInsertCustomizer.get().apply(insertInput.getRecords()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -319,7 +321,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
{
|
||||
if(record.getValue(requiredField.getName()) == null || (requiredField.getType().isStringLike() && record.getValueString(requiredField.getName()).trim().equals("")))
|
||||
{
|
||||
record.addError(new BadInputStatusMessage("Missing value in required field: " + Objects.requireNonNullElse(requiredField.getLabel(), requiredField.getName())));
|
||||
record.addError(new BadInputStatusMessage("Missing value in required field: " + requiredField.getLabel()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,8 +31,8 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostQueryCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.BufferedRecordPipe;
|
||||
@ -73,7 +73,7 @@ public class QueryAction
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QueryAction.class);
|
||||
|
||||
private Optional<TableCustomizerInterface> postQueryRecordCustomizer;
|
||||
private Optional<AbstractPostQueryCustomizer> postQueryRecordCustomizer;
|
||||
|
||||
private QueryInput queryInput;
|
||||
private QueryInterface queryInterface;
|
||||
@ -100,7 +100,7 @@ public class QueryAction
|
||||
}
|
||||
|
||||
QBackendMetaData backend = queryInput.getBackend();
|
||||
postQueryRecordCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.POST_QUERY_RECORD.getRole());
|
||||
postQueryRecordCustomizer = QCodeLoader.getTableCustomizer(AbstractPostQueryCustomizer.class, table, TableCustomizers.POST_QUERY_RECORD.getRole());
|
||||
this.queryInput = queryInput;
|
||||
|
||||
if(queryInput.getRecordPipe() != null)
|
||||
@ -264,7 +264,7 @@ public class QueryAction
|
||||
{
|
||||
if(this.postQueryRecordCustomizer.isPresent())
|
||||
{
|
||||
records = postQueryRecordCustomizer.get().postQuery(queryInput, records);
|
||||
records = postQueryRecordCustomizer.get().apply(records);
|
||||
}
|
||||
|
||||
if(queryInput.getShouldTranslatePossibleValues())
|
||||
|
@ -34,8 +34,9 @@ import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.audits.DMLAuditAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationStatusUpdater;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostUpdateCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreUpdateCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.UpdateInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.ValidateRecordSecurityLockHelper;
|
||||
@ -191,12 +192,14 @@ public class UpdateAction
|
||||
//////////////////////////////////////////////////////////////
|
||||
// finally, run the post-update customizer, if there is one //
|
||||
//////////////////////////////////////////////////////////////
|
||||
Optional<TableCustomizerInterface> postUpdateCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.POST_UPDATE_RECORD.getRole());
|
||||
Optional<AbstractPostUpdateCustomizer> postUpdateCustomizer = QCodeLoader.getTableCustomizer(AbstractPostUpdateCustomizer.class, table, TableCustomizers.POST_UPDATE_RECORD.getRole());
|
||||
if(postUpdateCustomizer.isPresent())
|
||||
{
|
||||
try
|
||||
{
|
||||
updateOutput.setRecords(postUpdateCustomizer.get().postUpdate(updateInput, updateOutput.getRecords(), oldRecordList));
|
||||
postUpdateCustomizer.get().setUpdateInput(updateInput);
|
||||
oldRecordList.ifPresent(l -> postUpdateCustomizer.get().setOldRecordList(l));
|
||||
updateOutput.setRecords(postUpdateCustomizer.get().apply(updateOutput.getRecords()));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@ -270,10 +273,13 @@ public class UpdateAction
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// after all validations, run the pre-update customizer, if there is one //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
Optional<TableCustomizerInterface> preUpdateCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.PRE_UPDATE_RECORD.getRole());
|
||||
Optional<AbstractPreUpdateCustomizer> preUpdateCustomizer = QCodeLoader.getTableCustomizer(AbstractPreUpdateCustomizer.class, table, TableCustomizers.PRE_UPDATE_RECORD.getRole());
|
||||
if(preUpdateCustomizer.isPresent())
|
||||
{
|
||||
updateInput.setRecords(preUpdateCustomizer.get().preUpdate(updateInput, updateInput.getRecords(), isPreview, oldRecordList));
|
||||
preUpdateCustomizer.get().setUpdateInput(updateInput);
|
||||
preUpdateCustomizer.get().setIsPreview(isPreview);
|
||||
oldRecordList.ifPresent(l -> preUpdateCustomizer.get().setOldRecordList(l));
|
||||
updateInput.setRecords(preUpdateCustomizer.get().apply(updateInput.getRecords()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -269,10 +269,6 @@ public class QPossibleValueTranslator
|
||||
{
|
||||
value = ValueUtils.getValueAsInteger(value);
|
||||
}
|
||||
if(field.getType().equals(QFieldType.LONG) && !(value instanceof Long))
|
||||
{
|
||||
value = ValueUtils.getValueAsLong(value);
|
||||
}
|
||||
}
|
||||
catch(QValueException e)
|
||||
{
|
||||
|
@ -490,13 +490,6 @@ public class QValueFormatter
|
||||
{
|
||||
adornmentValues = fileDownloadAdornment.get().getValues();
|
||||
}
|
||||
else
|
||||
{
|
||||
///////////////////////////////////////////////////////
|
||||
// don't change blobs unless they are file-downloads //
|
||||
///////////////////////////////////////////////////////
|
||||
continue;
|
||||
}
|
||||
|
||||
String fileNameField = ValueUtils.getValueAsString(adornmentValues.get(AdornmentType.FileDownloadValues.FILE_NAME_FIELD));
|
||||
String fileNameFormat = ValueUtils.getValueAsString(adornmentValues.get(AdornmentType.FileDownloadValues.FILE_NAME_FORMAT));
|
||||
|
@ -34,11 +34,5 @@ import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
*******************************************************************************/
|
||||
public record CapturedContext(QInstance qInstance, QSession qSession, QBackendTransaction qBackendTransaction, Stack<AbstractActionInput> actionStack)
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Simpler constructor
|
||||
*******************************************************************************/
|
||||
public CapturedContext(QInstance qInstance, QSession qSession)
|
||||
{
|
||||
this(qInstance, qSession, null, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -34,7 +34,6 @@ import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeVoidVoidMethod;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
@ -55,7 +54,6 @@ public class QContext
|
||||
private static ThreadLocal<Map<String, Serializable>> objectsThreadLocal = new ThreadLocal<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** private constructor - class is not meant to be instantiated.
|
||||
*******************************************************************************/
|
||||
@ -107,25 +105,6 @@ public class QContext
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static <T extends Throwable> void withTemporaryContext(CapturedContext context, UnsafeVoidVoidMethod<T> method) throws T
|
||||
{
|
||||
CapturedContext originalContext = QContext.capture();
|
||||
try
|
||||
{
|
||||
QContext.init(context);
|
||||
method.run();
|
||||
}
|
||||
finally
|
||||
{
|
||||
QContext.init(originalContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Init a new thread with the context captured from a different thread. e.g.,
|
||||
** when starting some async task.
|
||||
@ -288,7 +267,6 @@ public class QContext
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** get one named object from the Context for the current thread. may return null.
|
||||
*******************************************************************************/
|
||||
@ -302,7 +280,6 @@ public class QContext
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** get one named object from the Context for the current thread, cast to the
|
||||
** specified type if possible. if not found, or wrong type, empty is returned.
|
||||
|
@ -1030,50 +1030,6 @@ public class QInstanceEnricher
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Do a default mapping from an underscore_style field name to a camelCase name.
|
||||
**
|
||||
** Examples:
|
||||
** <ul>
|
||||
** <li>word_another_word_more_words -> wordAnotherWordMoreWords</li>
|
||||
** <li>l_ul_ul_ul -> lUlUlUl</li>
|
||||
** <li>tla_first -> tlaFirst</li>
|
||||
** <li>word_then_tla_in_middle -> wordThenTlaInMiddle</li>
|
||||
** <li>end_with_tla -> endWithTla</li>
|
||||
** <li>tla_and_another_tla -> tlaAndAnotherTla</li>
|
||||
** <li>ALL_CAPS -> allCaps</li>
|
||||
** </ul>
|
||||
*******************************************************************************/
|
||||
public static String inferNameFromBackendName(String backendName)
|
||||
{
|
||||
StringBuilder rs = new StringBuilder();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
// build a list of words in the name, then join them with _ and lower-case the result //
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
String[] words = backendName.toLowerCase(Locale.ROOT).split("_");
|
||||
for(int i = 0; i < words.length; i++)
|
||||
{
|
||||
String word = words[i];
|
||||
if(i == 0)
|
||||
{
|
||||
rs.append(word);
|
||||
}
|
||||
else
|
||||
{
|
||||
rs.append(word.substring(0, 1).toUpperCase());
|
||||
if(word.length() > 1)
|
||||
{
|
||||
rs.append(word.substring(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (rs.toString());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** If a app didn't have any sections, generate "sensible defaults"
|
||||
*******************************************************************************/
|
||||
|
@ -26,7 +26,6 @@ import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@ -36,7 +35,6 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
|
||||
@ -106,7 +104,6 @@ import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeLambda;
|
||||
import org.quartz.CronExpression;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -435,11 +432,6 @@ public class QInstanceValidator
|
||||
assertCondition(qInstance.getProcesses() != null && qInstance.getProcess(queue.getProcessName()) != null, "Unrecognized processName for queue: " + name);
|
||||
}
|
||||
|
||||
if(queue.getSchedule() != null)
|
||||
{
|
||||
validateScheduleMetaData(queue.getSchedule(), qInstance, "SQSQueueProvider " + name + ", schedule: ");
|
||||
}
|
||||
|
||||
runPlugins(QQueueMetaData.class, queue, qInstance);
|
||||
});
|
||||
}
|
||||
@ -1021,11 +1013,6 @@ public class QInstanceValidator
|
||||
assertCondition(qInstance.getAutomationProvider(providerName) != null, " has an unrecognized providerName: " + providerName);
|
||||
}
|
||||
|
||||
if(automationDetails.getSchedule() != null)
|
||||
{
|
||||
validateScheduleMetaData(automationDetails.getSchedule(), qInstance, prefix + " automationDetails, schedule: ");
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// validate the status tracking //
|
||||
//////////////////////////////////
|
||||
@ -1413,17 +1400,13 @@ public class QInstanceValidator
|
||||
if(process.getSchedule() != null)
|
||||
{
|
||||
QScheduleMetaData schedule = process.getSchedule();
|
||||
validateScheduleMetaData(schedule, qInstance, "Process " + processName + ", schedule: ");
|
||||
}
|
||||
assertCondition(schedule.getRepeatMillis() != null || schedule.getRepeatSeconds() != null, "Either repeat millis or repeat seconds must be set on schedule in process " + processName);
|
||||
|
||||
if(process.getVariantBackend() != null)
|
||||
{
|
||||
assertCondition(qInstance.getBackend(process.getVariantBackend()) != null, "Process " + processName + ", a variant backend was not found named " + process.getVariantBackend());
|
||||
assertCondition(process.getVariantRunStrategy() != null, "A variant run strategy was not set for process " + processName + " (which does specify a variant backend)");
|
||||
}
|
||||
else
|
||||
{
|
||||
assertCondition(process.getVariantRunStrategy() == null, "A variant run strategy was set for process " + processName + " (which isn't allowed, since it does not specify a variant backend)");
|
||||
if(schedule.getVariantBackend() != null)
|
||||
{
|
||||
assertCondition(qInstance.getBackend(schedule.getVariantBackend()) != null, "A variant backend was not found for " + schedule.getVariantBackend());
|
||||
assertCondition(schedule.getVariantRunStrategy() != null, "A variant run strategy was not set for " + schedule.getVariantBackend() + " on schedule in process " + processName);
|
||||
}
|
||||
}
|
||||
|
||||
for(QSupplementalProcessMetaData supplementalProcessMetaData : CollectionUtils.nonNullMap(process.getSupplementalMetaData()).values())
|
||||
@ -1438,50 +1421,6 @@ public class QInstanceValidator
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validateScheduleMetaData(QScheduleMetaData schedule, QInstance qInstance, String prefix)
|
||||
{
|
||||
boolean isRepeat = schedule.getRepeatMillis() != null || schedule.getRepeatSeconds() != null;
|
||||
boolean isCron = StringUtils.hasContent(schedule.getCronExpression());
|
||||
assertCondition(isRepeat || isCron, prefix + " either repeatMillis or repeatSeconds or cronExpression must be set");
|
||||
assertCondition(!(isRepeat && isCron), prefix + " both a repeat time and cronExpression may not be set");
|
||||
|
||||
if(isCron)
|
||||
{
|
||||
boolean hasDelay = schedule.getInitialDelayMillis() != null || schedule.getInitialDelaySeconds() != null;
|
||||
assertCondition(!hasDelay, prefix + " a cron schedule may not have an initial delay");
|
||||
|
||||
try
|
||||
{
|
||||
CronExpression.validateExpression(schedule.getCronExpression());
|
||||
}
|
||||
catch(ParseException pe)
|
||||
{
|
||||
errors.add(prefix + " invalid cron expression: " + pe.getMessage());
|
||||
}
|
||||
|
||||
if(assertCondition(StringUtils.hasContent(schedule.getCronTimeZoneId()), prefix + " a cron schedule must specify a cronTimeZoneId"))
|
||||
{
|
||||
String[] availableIDs = TimeZone.getAvailableIDs();
|
||||
Optional<String> first = Arrays.stream(availableIDs).filter(id -> id.equals(schedule.getCronTimeZoneId())).findFirst();
|
||||
assertCondition(first.isPresent(), prefix + " unrecognized cronTimeZoneId: " + schedule.getCronTimeZoneId());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
assertCondition(!StringUtils.hasContent(schedule.getCronTimeZoneId()), prefix + " a non-cron schedule must not specify a cronTimeZoneId");
|
||||
}
|
||||
|
||||
if(assertCondition(StringUtils.hasContent(schedule.getSchedulerName()), prefix + " is missing a scheduler name"))
|
||||
{
|
||||
assertCondition(qInstance.getScheduler(schedule.getSchedulerName()) != null, prefix + " is referencing an unknown scheduler name: " + schedule.getSchedulerName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -48,17 +48,6 @@ public class CollectedLogMessage
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "CollectedLogMessage{level=" + level + ", message='" + message + '\'' + ", exception=" + exception + '}';
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for message
|
||||
*******************************************************************************/
|
||||
|
@ -60,11 +60,6 @@ public interface QueryOrGetInputInterface
|
||||
QBackendTransaction getTransaction();
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
String getTableName();
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for transaction
|
||||
*******************************************************************************/
|
||||
|
@ -24,7 +24,6 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.aggregate;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
|
||||
|
||||
@ -39,17 +38,6 @@ public class GroupBy implements Serializable
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public GroupBy(QFieldMetaData field)
|
||||
{
|
||||
this.type = field.getType();
|
||||
this.fieldName = field.getName();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -23,12 +23,10 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -91,18 +89,4 @@ public class QueryOutput extends AbstractActionOutput implements Serializable
|
||||
return storage.getRecords();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public <T extends QRecordEntity> List<T> getRecordEntities(Class<T> entityClass) throws QException
|
||||
{
|
||||
List<T> rs = new ArrayList<>();
|
||||
for(QRecord record : storage.getRecords())
|
||||
{
|
||||
rs.add(QRecordEntity.fromQRecord(entityClass, record));
|
||||
}
|
||||
return (rs);
|
||||
}
|
||||
}
|
||||
|
@ -1,78 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.common;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PVSValueFormatAndFields;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class TimeZonePossibleValueSourceMetaDataProvider
|
||||
{
|
||||
public static final String NAME = "timeZones";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource produce()
|
||||
{
|
||||
return (produce(null, null));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource produce(Predicate<String> filter, Function<String, String> labelMapper)
|
||||
{
|
||||
QPossibleValueSource possibleValueSource = new QPossibleValueSource()
|
||||
.withName("timeZones")
|
||||
.withType(QPossibleValueSourceType.ENUM)
|
||||
.withValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY);
|
||||
|
||||
List<QPossibleValue<?>> enumValues = new ArrayList<>();
|
||||
for(String availableID : TimeZone.getAvailableIDs())
|
||||
{
|
||||
if(filter == null || filter.test(availableID))
|
||||
{
|
||||
String label = labelMapper == null ? availableID : labelMapper.apply(availableID);
|
||||
enumValues.add(new QPossibleValue<>(availableID, label));
|
||||
}
|
||||
}
|
||||
|
||||
possibleValueSource.withEnumValues(enumValues);
|
||||
return (possibleValueSource);
|
||||
}
|
||||
}
|
@ -462,16 +462,6 @@ public class QRecord implements Serializable
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for a single field's value
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Long getValueLong(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsLong(values.get(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
|
@ -53,10 +53,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueProviderMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QSchedulerMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.SchedulableType;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import io.github.cdimascio.dotenv.Dotenv;
|
||||
@ -93,9 +91,6 @@ public class QInstance
|
||||
private Map<String, QQueueProviderMetaData> queueProviders = new LinkedHashMap<>();
|
||||
private Map<String, QQueueMetaData> queues = new LinkedHashMap<>();
|
||||
|
||||
private Map<String, QSchedulerMetaData> schedulers = new LinkedHashMap<>();
|
||||
private Map<String, SchedulableType> schedulableTypes = new LinkedHashMap<>();
|
||||
|
||||
private Map<String, QSupplementalInstanceMetaData> supplementalMetaData = new LinkedHashMap<>();
|
||||
|
||||
private String deploymentMode;
|
||||
@ -1229,106 +1224,4 @@ public class QInstance
|
||||
metaData.addSelfToInstance(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addScheduler(QSchedulerMetaData scheduler)
|
||||
{
|
||||
String name = scheduler.getName();
|
||||
if(!StringUtils.hasContent(name))
|
||||
{
|
||||
throw (new IllegalArgumentException("Attempted to add a scheduler without a name."));
|
||||
}
|
||||
if(this.schedulers.containsKey(name))
|
||||
{
|
||||
throw (new IllegalArgumentException("Attempted to add a second scheduler with name: " + name));
|
||||
}
|
||||
this.schedulers.put(name, scheduler);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QSchedulerMetaData getScheduler(String name)
|
||||
{
|
||||
return (this.schedulers.get(name));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for schedulers
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, QSchedulerMetaData> getSchedulers()
|
||||
{
|
||||
return schedulers;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for schedulers
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setSchedulers(Map<String, QSchedulerMetaData> schedulers)
|
||||
{
|
||||
this.schedulers = schedulers;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addSchedulableType(SchedulableType schedulableType)
|
||||
{
|
||||
String name = schedulableType.getName();
|
||||
if(!StringUtils.hasContent(name))
|
||||
{
|
||||
throw (new IllegalArgumentException("Attempted to add a schedulableType without a name."));
|
||||
}
|
||||
if(this.schedulableTypes.containsKey(name))
|
||||
{
|
||||
throw (new IllegalArgumentException("Attempted to add a second schedulableType with name: " + name));
|
||||
}
|
||||
this.schedulableTypes.put(name, schedulableType);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public SchedulableType getSchedulableType(String name)
|
||||
{
|
||||
return (this.schedulableTypes.get(name));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for schedulableTypes
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, SchedulableType> getSchedulableTypes()
|
||||
{
|
||||
return schedulableTypes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for schedulableTypes
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setSchedulableTypes(Map<String, SchedulableType> schedulableTypes)
|
||||
{
|
||||
this.schedulableTypes = schedulableTypes;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,11 +29,6 @@ package com.kingsrook.qqq.backend.core.model.metadata;
|
||||
public interface TopLevelMetaDataInterface
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
String getName();
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.automation;
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -34,6 +35,8 @@ public class QAutomationProviderMetaData implements TopLevelMetaDataInterface
|
||||
private String name;
|
||||
private QAutomationProviderType type;
|
||||
|
||||
private QScheduleMetaData schedule;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -104,6 +107,40 @@ public class QAutomationProviderMetaData implements TopLevelMetaDataInterface
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for schedule
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QScheduleMetaData getSchedule()
|
||||
{
|
||||
return schedule;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for schedule
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setSchedule(QScheduleMetaData schedule)
|
||||
{
|
||||
this.schedule = schedule;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for schedule
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QAutomationProviderMetaData withSchedule(QScheduleMetaData schedule)
|
||||
{
|
||||
this.schedule = schedule;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -55,17 +55,6 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return "Branding";
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for companyName
|
||||
**
|
||||
|
@ -37,7 +37,6 @@ public enum QFieldType
|
||||
{
|
||||
STRING,
|
||||
INTEGER,
|
||||
LONG,
|
||||
DECIMAL,
|
||||
BOOLEAN,
|
||||
DATE,
|
||||
@ -66,10 +65,6 @@ public enum QFieldType
|
||||
{
|
||||
return (INTEGER);
|
||||
}
|
||||
if(c.equals(Long.class) || c.equals(long.class))
|
||||
{
|
||||
return (LONG);
|
||||
}
|
||||
if(c.equals(BigDecimal.class))
|
||||
{
|
||||
return (DECIMAL);
|
||||
@ -115,7 +110,7 @@ public enum QFieldType
|
||||
*******************************************************************************/
|
||||
public boolean isNumeric()
|
||||
{
|
||||
return this == QFieldType.INTEGER || this == QFieldType.LONG || this == QFieldType.DECIMAL;
|
||||
return this == QFieldType.INTEGER || this == QFieldType.DECIMAL;
|
||||
}
|
||||
|
||||
|
||||
|
@ -22,7 +22,6 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.frontend;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
@ -61,7 +60,6 @@ public class QFrontendWidgetMetaData
|
||||
|
||||
protected Map<String, QIcon> icons;
|
||||
protected Map<String, QHelpContent> helpContent;
|
||||
protected Map<String, Serializable> defaultValues;
|
||||
|
||||
private final boolean hasPermission;
|
||||
|
||||
@ -97,7 +95,6 @@ public class QFrontendWidgetMetaData
|
||||
}
|
||||
|
||||
this.helpContent = widgetMetaData.getHelpContent();
|
||||
this.defaultValues = widgetMetaData.getDefaultValues();
|
||||
|
||||
hasPermission = PermissionsHelper.hasWidgetPermission(actionInput, name);
|
||||
}
|
||||
@ -277,16 +274,4 @@ public class QFrontendWidgetMetaData
|
||||
{
|
||||
return helpContent;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for defaultValues
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, Serializable> getDefaultValues()
|
||||
{
|
||||
return defaultValues;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,8 +23,6 @@ package com.kingsrook.qqq.backend.core.model.metadata.layout;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -365,11 +363,11 @@ public class QAppMetaData implements QAppChildMetaData, MetaDataWithPermissionRu
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QAppMetaData withSectionOfChildren(QAppSection section, Collection<? extends QAppChildMetaData> children)
|
||||
public QAppMetaData withSectionOfChildren(QAppSection section, QAppChildMetaData... children)
|
||||
{
|
||||
this.addSection(section);
|
||||
|
||||
for(QAppChildMetaData child : CollectionUtils.nonNullCollection(children))
|
||||
for(QAppChildMetaData child : children)
|
||||
{
|
||||
withChild(child);
|
||||
if(child instanceof QTableMetaData)
|
||||
@ -394,15 +392,6 @@ public class QAppMetaData implements QAppChildMetaData, MetaDataWithPermissionRu
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QAppMetaData withSectionOfChildren(QAppSection section, QAppChildMetaData... children)
|
||||
{
|
||||
return (withSectionOfChildren(section, children == null ? null : Arrays.stream(children).toList()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for permissionRules
|
||||
|
@ -155,26 +155,4 @@ public class AbstractProcessMetaDataBuilder
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AbstractProcessMetaDataBuilder withVariantRunStrategy(VariantRunStrategy variantRunStrategy)
|
||||
{
|
||||
processMetaData.setVariantRunStrategy(variantRunStrategy);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AbstractProcessMetaDataBuilder withVariantBackend(String variantBackend)
|
||||
{
|
||||
processMetaData.setVariantBackend(variantBackend);
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -64,9 +64,6 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
|
||||
|
||||
private QScheduleMetaData schedule;
|
||||
|
||||
private VariantRunStrategy variantRunStrategy;
|
||||
private String variantBackend;
|
||||
|
||||
private Map<String, QSupplementalProcessMetaData> supplementalMetaData;
|
||||
|
||||
|
||||
@ -674,66 +671,4 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantRunStrategy
|
||||
*******************************************************************************/
|
||||
public VariantRunStrategy getVariantRunStrategy()
|
||||
{
|
||||
return (this.variantRunStrategy);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantRunStrategy
|
||||
*******************************************************************************/
|
||||
public void setVariantRunStrategy(VariantRunStrategy variantRunStrategy)
|
||||
{
|
||||
this.variantRunStrategy = variantRunStrategy;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantRunStrategy
|
||||
*******************************************************************************/
|
||||
public QProcessMetaData withVariantRunStrategy(VariantRunStrategy variantRunStrategy)
|
||||
{
|
||||
this.variantRunStrategy = variantRunStrategy;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantBackend
|
||||
*******************************************************************************/
|
||||
public String getVariantBackend()
|
||||
{
|
||||
return (this.variantBackend);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantBackend
|
||||
*******************************************************************************/
|
||||
public void setVariantBackend(String variantBackend)
|
||||
{
|
||||
this.variantBackend = variantBackend;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantBackend
|
||||
*******************************************************************************/
|
||||
public QProcessMetaData withVariantBackend(String variantBackend)
|
||||
{
|
||||
this.variantBackend = variantBackend;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,32 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.processes;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public enum VariantRunStrategy
|
||||
{
|
||||
PARALLEL,
|
||||
SERIAL
|
||||
}
|
@ -22,6 +22,9 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.queues;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Meta-data for an source of Amazon SQS queues (e.g, an aws account/credential
|
||||
** set, with a common base URL).
|
||||
@ -36,6 +39,8 @@ public class SQSQueueProviderMetaData extends QQueueProviderMetaData
|
||||
private String region;
|
||||
private String baseURL;
|
||||
|
||||
private QScheduleMetaData schedule;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -196,4 +201,38 @@ public class SQSQueueProviderMetaData extends QQueueProviderMetaData
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for schedule
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QScheduleMetaData getSchedule()
|
||||
{
|
||||
return schedule;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for schedule
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setSchedule(QScheduleMetaData schedule)
|
||||
{
|
||||
this.schedule = schedule;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for schedule
|
||||
**
|
||||
*******************************************************************************/
|
||||
public SQSQueueProviderMetaData withSchedule(QScheduleMetaData schedule)
|
||||
{
|
||||
this.schedule = schedule;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,41 +22,29 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.scheduleing;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Meta-data to define scheduled actions within QQQ.
|
||||
**
|
||||
** Supports repeating jobs, either on a given # of seconds or millis, or cron
|
||||
** expressions (though cron may not be supported by all schedulers!)
|
||||
**
|
||||
** Initially, only supports repeating jobs, either on a given # of seconds or millis.
|
||||
** Can also specify an initialDelay - e.g., to avoid all jobs starting up at the
|
||||
** same moment.
|
||||
**
|
||||
** In the future we most likely would want to allow cron strings to be added here.
|
||||
*******************************************************************************/
|
||||
public class QScheduleMetaData
|
||||
{
|
||||
private String schedulerName;
|
||||
private String description;
|
||||
public enum RunStrategy
|
||||
{PARALLEL, SERIAL}
|
||||
|
||||
|
||||
|
||||
private Integer repeatSeconds;
|
||||
private Integer repeatMillis;
|
||||
private Integer initialDelaySeconds;
|
||||
private Integer initialDelayMillis;
|
||||
|
||||
private String cronExpression;
|
||||
private String cronTimeZoneId;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public boolean isCron()
|
||||
{
|
||||
return StringUtils.hasContent(cronExpression);
|
||||
}
|
||||
private RunStrategy variantRunStrategy;
|
||||
private String variantBackend;
|
||||
|
||||
|
||||
|
||||
@ -197,125 +185,63 @@ public class QScheduleMetaData
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for cronExpression
|
||||
** Getter for variantBackend
|
||||
*******************************************************************************/
|
||||
public String getCronExpression()
|
||||
public String getVariantBackend()
|
||||
{
|
||||
return (this.cronExpression);
|
||||
return (this.variantBackend);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for cronExpression
|
||||
** Setter for variantBackend
|
||||
*******************************************************************************/
|
||||
public void setCronExpression(String cronExpression)
|
||||
public void setVariantBackend(String variantBackend)
|
||||
{
|
||||
this.cronExpression = cronExpression;
|
||||
this.variantBackend = variantBackend;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for cronExpression
|
||||
** Fluent setter for variantBackend
|
||||
*******************************************************************************/
|
||||
public QScheduleMetaData withCronExpression(String cronExpression)
|
||||
public QScheduleMetaData withBackendVariant(String backendVariant)
|
||||
{
|
||||
this.cronExpression = cronExpression;
|
||||
this.variantBackend = backendVariant;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for cronTimeZoneId
|
||||
** Getter for variantRunStrategy
|
||||
*******************************************************************************/
|
||||
public String getCronTimeZoneId()
|
||||
public RunStrategy getVariantRunStrategy()
|
||||
{
|
||||
return (this.cronTimeZoneId);
|
||||
return (this.variantRunStrategy);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for cronTimeZoneId
|
||||
** Setter for variantRunStrategy
|
||||
*******************************************************************************/
|
||||
public void setCronTimeZoneId(String cronTimeZoneId)
|
||||
public void setVariantRunStrategy(RunStrategy variantRunStrategy)
|
||||
{
|
||||
this.cronTimeZoneId = cronTimeZoneId;
|
||||
this.variantRunStrategy = variantRunStrategy;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for cronTimeZoneId
|
||||
** Fluent setter for variantRunStrategy
|
||||
*******************************************************************************/
|
||||
public QScheduleMetaData withCronTimeZoneId(String cronTimeZoneId)
|
||||
public QScheduleMetaData withVariantRunStrategy(RunStrategy variantRunStrategy)
|
||||
{
|
||||
this.cronTimeZoneId = cronTimeZoneId;
|
||||
this.variantRunStrategy = variantRunStrategy;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for schedulerName
|
||||
*******************************************************************************/
|
||||
public String getSchedulerName()
|
||||
{
|
||||
return (this.schedulerName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for schedulerName
|
||||
*******************************************************************************/
|
||||
public void setSchedulerName(String schedulerName)
|
||||
{
|
||||
this.schedulerName = schedulerName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for schedulerName
|
||||
*******************************************************************************/
|
||||
public QScheduleMetaData withSchedulerName(String schedulerName)
|
||||
{
|
||||
this.schedulerName = schedulerName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for description
|
||||
*******************************************************************************/
|
||||
public String getDescription()
|
||||
{
|
||||
return (this.description);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for description
|
||||
*******************************************************************************/
|
||||
public void setDescription(String description)
|
||||
{
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for description
|
||||
*******************************************************************************/
|
||||
public QScheduleMetaData withDescription(String description)
|
||||
{
|
||||
this.description = description;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,131 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.scheduleing;
|
||||
|
||||
|
||||
import java.util.function.Supplier;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.QSchedulerInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public abstract class QSchedulerMetaData implements TopLevelMetaDataInterface
|
||||
{
|
||||
private String name;
|
||||
private String type;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public boolean supportsCronSchedules()
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public abstract QSchedulerInterface initSchedulerInstance(QInstance qInstance, Supplier<QSession> systemSessionSupplier) throws QException;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for name
|
||||
*******************************************************************************/
|
||||
public String getName()
|
||||
{
|
||||
return (this.name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for name
|
||||
*******************************************************************************/
|
||||
public void setName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for name
|
||||
*******************************************************************************/
|
||||
public QSchedulerMetaData withName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for type
|
||||
*******************************************************************************/
|
||||
public String getType()
|
||||
{
|
||||
return (this.type);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for type
|
||||
*******************************************************************************/
|
||||
public void setType(String type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for type
|
||||
*******************************************************************************/
|
||||
public QSchedulerMetaData withType(String type)
|
||||
{
|
||||
this.type = type;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void addSelfToInstance(QInstance qInstance)
|
||||
{
|
||||
qInstance.addScheduler(this);
|
||||
}
|
||||
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.scheduleing.quartz;
|
||||
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.function.Supplier;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
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.scheduleing.QSchedulerMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.QSchedulerInterface;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzScheduler;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QuartzSchedulerMetaData extends QSchedulerMetaData
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QuartzSchedulerMetaData.class);
|
||||
|
||||
public static final String TYPE = "quartz";
|
||||
|
||||
private Properties properties;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QuartzSchedulerMetaData()
|
||||
{
|
||||
setType(TYPE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public boolean supportsCronSchedules()
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QSchedulerInterface initSchedulerInstance(QInstance qInstance, Supplier<QSession> systemSessionSupplier) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
QuartzScheduler quartzScheduler = QuartzScheduler.initInstance(qInstance, getName(), getProperties(), systemSessionSupplier);
|
||||
return (quartzScheduler);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error initializing quartz scheduler", e);
|
||||
throw (new QException("Error initializing quartz scheduler", e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for properties
|
||||
*******************************************************************************/
|
||||
public Properties getProperties()
|
||||
{
|
||||
return (this.properties);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for properties
|
||||
*******************************************************************************/
|
||||
public void setProperties(Properties properties)
|
||||
{
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for properties
|
||||
*******************************************************************************/
|
||||
public QuartzSchedulerMetaData withProperties(Properties properties)
|
||||
{
|
||||
this.properties = properties;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.scheduleing.simple;
|
||||
|
||||
|
||||
import java.util.function.Supplier;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QSchedulerMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.QSchedulerInterface;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.simple.SimpleScheduler;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class SimpleSchedulerMetaData extends QSchedulerMetaData
|
||||
{
|
||||
public static final String TYPE = "simple";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public SimpleSchedulerMetaData()
|
||||
{
|
||||
setType(TYPE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public boolean supportsCronSchedules()
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QSchedulerInterface initSchedulerInstance(QInstance qInstance, Supplier<QSession> systemSessionSupplier)
|
||||
{
|
||||
SimpleScheduler simpleScheduler = SimpleScheduler.getInstance(qInstance);
|
||||
simpleScheduler.setSessionSupplier(systemSessionSupplier);
|
||||
simpleScheduler.setSchedulerName(getName());
|
||||
return simpleScheduler;
|
||||
}
|
||||
|
||||
}
|
@ -22,10 +22,6 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.tables;
|
||||
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Things that can be done to tables, fields.
|
||||
**
|
||||
@ -42,26 +38,5 @@ public enum Capability
|
||||
// keep these values in sync with Capability.ts in qqq-frontend-core //
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
||||
QUERY_STATS;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static Set<Capability> allReadCapabilities()
|
||||
{
|
||||
return (new HashSet<>(Set.of(TABLE_QUERY, TABLE_GET, TABLE_COUNT, QUERY_STATS)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static Set<Capability> allWriteCapabilities()
|
||||
{
|
||||
return (new HashSet<>(Set.of(TABLE_INSERT, TABLE_UPDATE, TABLE_DELETE)));
|
||||
}
|
||||
|
||||
QUERY_STATS
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ package com.kingsrook.qqq.backend.core.model.metadata.tables.automation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -38,8 +37,6 @@ public class QTableAutomationDetails
|
||||
|
||||
private Integer overrideBatchSize;
|
||||
|
||||
private QScheduleMetaData schedule;
|
||||
|
||||
private String shardByFieldName; // field in "this" table, to use for sharding
|
||||
private String shardSourceTableName; // name of the table where the shards are defined as rows
|
||||
private String shardLabelFieldName; // field in shard-source-table to use for labeling shards
|
||||
@ -320,35 +317,4 @@ public class QTableAutomationDetails
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for schedule
|
||||
*******************************************************************************/
|
||||
public QScheduleMetaData getSchedule()
|
||||
{
|
||||
return (this.schedule);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for schedule
|
||||
*******************************************************************************/
|
||||
public void setSchedule(QScheduleMetaData schedule)
|
||||
{
|
||||
this.schedule = schedule;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for schedule
|
||||
*******************************************************************************/
|
||||
public QTableAutomationDetails withSchedule(QScheduleMetaData schedule)
|
||||
{
|
||||
this.schedule = schedule;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -65,7 +65,6 @@ public class QueryStatMetaDataProvider
|
||||
instance.addTable(defineStandardTable(QueryStatJoinTable.TABLE_NAME, QueryStatJoinTable.class, backendName, backendDetailEnricher));
|
||||
|
||||
instance.addTable(defineStandardTable(QueryStatCriteriaField.TABLE_NAME, QueryStatCriteriaField.class, backendName, backendDetailEnricher)
|
||||
.withIcon(new QIcon().withName("filter_alt"))
|
||||
.withExposedJoin(new ExposedJoin().withJoinTable(QueryStat.TABLE_NAME))
|
||||
);
|
||||
|
||||
@ -116,7 +115,6 @@ public class QueryStatMetaDataProvider
|
||||
|
||||
QTableMetaData table = new QTableMetaData()
|
||||
.withName(QueryStat.TABLE_NAME)
|
||||
.withIcon(new QIcon().withName("query_stats"))
|
||||
.withBackendName(backendName)
|
||||
.withAuditRules(new QAuditRules().withAuditLevel(AuditLevel.NONE))
|
||||
.withRecordLabelFormat("%s")
|
||||
|
@ -1,496 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.scheduledjobs;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.common.TimeZonePossibleValueSourceMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QAssociation;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.MutableMap;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ScheduledJob extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "scheduledJob";
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Integer id;
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Instant createDate;
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Instant modifyDate;
|
||||
|
||||
@QField(isRequired = true, maxLength = 100, valueTooLongBehavior = ValueTooLongBehavior.TRUNCATE)
|
||||
private String label;
|
||||
|
||||
@QField(maxLength = 250, valueTooLongBehavior = ValueTooLongBehavior.TRUNCATE_ELLIPSIS)
|
||||
private String description;
|
||||
|
||||
@QField(isRequired = true, label = "Scheduler", maxLength = 100, valueTooLongBehavior = ValueTooLongBehavior.ERROR, possibleValueSourceName = SchedulersPossibleValueSource.NAME)
|
||||
private String schedulerName;
|
||||
|
||||
@QField(maxLength = 100, valueTooLongBehavior = ValueTooLongBehavior.ERROR)
|
||||
private String cronExpression;
|
||||
|
||||
@QField(maxLength = 100, valueTooLongBehavior = ValueTooLongBehavior.ERROR, possibleValueSourceName = TimeZonePossibleValueSourceMetaDataProvider.NAME)
|
||||
private String cronTimeZoneId;
|
||||
|
||||
@QField(displayFormat = DisplayFormat.COMMAS)
|
||||
private Integer repeatSeconds;
|
||||
|
||||
@QField(isRequired = true, maxLength = 100, valueTooLongBehavior = ValueTooLongBehavior.ERROR, possibleValueSourceName = ScheduledJobType.NAME)
|
||||
private String type;
|
||||
|
||||
@QField(isRequired = true)
|
||||
private Boolean isActive;
|
||||
|
||||
@QAssociation(name = ScheduledJobParameter.TABLE_NAME)
|
||||
private List<ScheduledJobParameter> jobParameters;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ScheduledJob()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ScheduledJob(QRecord qRecord) throws QException
|
||||
{
|
||||
populateFromQRecord(qRecord);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for id
|
||||
*******************************************************************************/
|
||||
public Integer getId()
|
||||
{
|
||||
return (this.id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for id
|
||||
*******************************************************************************/
|
||||
public void setId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for id
|
||||
*******************************************************************************/
|
||||
public ScheduledJob withId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for createDate
|
||||
*******************************************************************************/
|
||||
public Instant getCreateDate()
|
||||
{
|
||||
return (this.createDate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for createDate
|
||||
*******************************************************************************/
|
||||
public void setCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for createDate
|
||||
*******************************************************************************/
|
||||
public ScheduledJob withCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for modifyDate
|
||||
*******************************************************************************/
|
||||
public Instant getModifyDate()
|
||||
{
|
||||
return (this.modifyDate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for modifyDate
|
||||
*******************************************************************************/
|
||||
public void setModifyDate(Instant modifyDate)
|
||||
{
|
||||
this.modifyDate = modifyDate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for modifyDate
|
||||
*******************************************************************************/
|
||||
public ScheduledJob withModifyDate(Instant modifyDate)
|
||||
{
|
||||
this.modifyDate = modifyDate;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for label
|
||||
*******************************************************************************/
|
||||
public String getLabel()
|
||||
{
|
||||
return (this.label);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for label
|
||||
*******************************************************************************/
|
||||
public void setLabel(String label)
|
||||
{
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for label
|
||||
*******************************************************************************/
|
||||
public ScheduledJob withLabel(String label)
|
||||
{
|
||||
this.label = label;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for description
|
||||
*******************************************************************************/
|
||||
public String getDescription()
|
||||
{
|
||||
return (this.description);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for description
|
||||
*******************************************************************************/
|
||||
public void setDescription(String description)
|
||||
{
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for description
|
||||
*******************************************************************************/
|
||||
public ScheduledJob withDescription(String description)
|
||||
{
|
||||
this.description = description;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for cronExpression
|
||||
*******************************************************************************/
|
||||
public String getCronExpression()
|
||||
{
|
||||
return (this.cronExpression);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for cronExpression
|
||||
*******************************************************************************/
|
||||
public void setCronExpression(String cronExpression)
|
||||
{
|
||||
this.cronExpression = cronExpression;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for cronExpression
|
||||
*******************************************************************************/
|
||||
public ScheduledJob withCronExpression(String cronExpression)
|
||||
{
|
||||
this.cronExpression = cronExpression;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for cronTimeZoneId
|
||||
*******************************************************************************/
|
||||
public String getCronTimeZoneId()
|
||||
{
|
||||
return (this.cronTimeZoneId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for cronTimeZoneId
|
||||
*******************************************************************************/
|
||||
public void setCronTimeZoneId(String cronTimeZoneId)
|
||||
{
|
||||
this.cronTimeZoneId = cronTimeZoneId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for cronTimeZoneId
|
||||
*******************************************************************************/
|
||||
public ScheduledJob withCronTimeZoneId(String cronTimeZoneId)
|
||||
{
|
||||
this.cronTimeZoneId = cronTimeZoneId;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for isActive
|
||||
*******************************************************************************/
|
||||
public Boolean getIsActive()
|
||||
{
|
||||
return (this.isActive);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for isActive
|
||||
*******************************************************************************/
|
||||
public void setIsActive(Boolean isActive)
|
||||
{
|
||||
this.isActive = isActive;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for isActive
|
||||
*******************************************************************************/
|
||||
public ScheduledJob withIsActive(Boolean isActive)
|
||||
{
|
||||
this.isActive = isActive;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for schedulerName
|
||||
*******************************************************************************/
|
||||
public String getSchedulerName()
|
||||
{
|
||||
return (this.schedulerName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for schedulerName
|
||||
*******************************************************************************/
|
||||
public void setSchedulerName(String schedulerName)
|
||||
{
|
||||
this.schedulerName = schedulerName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for schedulerName
|
||||
*******************************************************************************/
|
||||
public ScheduledJob withSchedulerName(String schedulerName)
|
||||
{
|
||||
this.schedulerName = schedulerName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for type
|
||||
*******************************************************************************/
|
||||
public String getType()
|
||||
{
|
||||
return (this.type);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for type
|
||||
*******************************************************************************/
|
||||
public void setType(String type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for type
|
||||
*******************************************************************************/
|
||||
public ScheduledJob withType(String type)
|
||||
{
|
||||
this.type = type;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for jobParameters
|
||||
*******************************************************************************/
|
||||
public List<ScheduledJobParameter> getJobParameters()
|
||||
{
|
||||
return (this.jobParameters);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for jobParameters - but a map of just the key=value pairs.
|
||||
*******************************************************************************/
|
||||
public Map<String, String> getJobParametersMap()
|
||||
{
|
||||
if(CollectionUtils.nullSafeIsEmpty(this.jobParameters))
|
||||
{
|
||||
return (new HashMap<>());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// wrap in mutable map, just to avoid any immutable or other bs from toMap's default //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
return new MutableMap<>(jobParameters.stream().collect(Collectors.toMap(ScheduledJobParameter::getKey, ScheduledJobParameter::getValue)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for jobParameters
|
||||
*******************************************************************************/
|
||||
public void setJobParameters(List<ScheduledJobParameter> jobParameters)
|
||||
{
|
||||
this.jobParameters = jobParameters;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for jobParameters
|
||||
*******************************************************************************/
|
||||
public ScheduledJob withJobParameters(List<ScheduledJobParameter> jobParameters)
|
||||
{
|
||||
this.jobParameters = jobParameters;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for repeatSeconds
|
||||
*******************************************************************************/
|
||||
public Integer getRepeatSeconds()
|
||||
{
|
||||
return (this.repeatSeconds);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for repeatSeconds
|
||||
*******************************************************************************/
|
||||
public void setRepeatSeconds(Integer repeatSeconds)
|
||||
{
|
||||
this.repeatSeconds = repeatSeconds;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for repeatSeconds
|
||||
*******************************************************************************/
|
||||
public ScheduledJob withRepeatSeconds(Integer repeatSeconds)
|
||||
{
|
||||
this.repeatSeconds = repeatSeconds;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,266 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.scheduledjobs;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ScheduledJobParameter extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "scheduledJobParameter";
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Integer id;
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Instant createDate;
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Instant modifyDate;
|
||||
|
||||
@QField(possibleValueSourceName = ScheduledJob.TABLE_NAME, isRequired = true)
|
||||
private Integer scheduledJobId;
|
||||
|
||||
@QField(maxLength = 250, valueTooLongBehavior = ValueTooLongBehavior.ERROR, isRequired = true)
|
||||
private String key;
|
||||
|
||||
@QField(maxLength = 250, valueTooLongBehavior = ValueTooLongBehavior.ERROR)
|
||||
private String value;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ScheduledJobParameter()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ScheduledJobParameter(QRecord qRecord) throws QException
|
||||
{
|
||||
populateFromQRecord(qRecord);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for id
|
||||
*******************************************************************************/
|
||||
public Integer getId()
|
||||
{
|
||||
return (this.id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for id
|
||||
*******************************************************************************/
|
||||
public void setId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for id
|
||||
*******************************************************************************/
|
||||
public ScheduledJobParameter withId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for createDate
|
||||
*******************************************************************************/
|
||||
public Instant getCreateDate()
|
||||
{
|
||||
return (this.createDate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for createDate
|
||||
*******************************************************************************/
|
||||
public void setCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for createDate
|
||||
*******************************************************************************/
|
||||
public ScheduledJobParameter withCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for modifyDate
|
||||
*******************************************************************************/
|
||||
public Instant getModifyDate()
|
||||
{
|
||||
return (this.modifyDate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for modifyDate
|
||||
*******************************************************************************/
|
||||
public void setModifyDate(Instant modifyDate)
|
||||
{
|
||||
this.modifyDate = modifyDate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for modifyDate
|
||||
*******************************************************************************/
|
||||
public ScheduledJobParameter withModifyDate(Instant modifyDate)
|
||||
{
|
||||
this.modifyDate = modifyDate;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for scheduledJobId
|
||||
*******************************************************************************/
|
||||
public Integer getScheduledJobId()
|
||||
{
|
||||
return (this.scheduledJobId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for scheduledJobId
|
||||
*******************************************************************************/
|
||||
public void setScheduledJobId(Integer scheduledJobId)
|
||||
{
|
||||
this.scheduledJobId = scheduledJobId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for scheduledJobId
|
||||
*******************************************************************************/
|
||||
public ScheduledJobParameter withScheduledJobId(Integer scheduledJobId)
|
||||
{
|
||||
this.scheduledJobId = scheduledJobId;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for key
|
||||
*******************************************************************************/
|
||||
public String getKey()
|
||||
{
|
||||
return (this.key);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for key
|
||||
*******************************************************************************/
|
||||
public void setKey(String key)
|
||||
{
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for key
|
||||
*******************************************************************************/
|
||||
public ScheduledJobParameter withKey(String key)
|
||||
{
|
||||
this.key = key;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for value
|
||||
*******************************************************************************/
|
||||
public String getValue()
|
||||
{
|
||||
return (this.value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for value
|
||||
*******************************************************************************/
|
||||
public void setValue(String value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for value
|
||||
*******************************************************************************/
|
||||
public ScheduledJobParameter withValue(String value)
|
||||
{
|
||||
this.value = value;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.scheduledjobs;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PossibleValueEnum;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public enum ScheduledJobType implements PossibleValueEnum<String>
|
||||
{
|
||||
PROCESS,
|
||||
QUEUE_PROCESSOR,
|
||||
TABLE_AUTOMATIONS,
|
||||
// todo - future - USER_REPORT
|
||||
;
|
||||
|
||||
public static final String NAME = "scheduledJobType";
|
||||
|
||||
private final String label;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
ScheduledJobType()
|
||||
{
|
||||
this.label = QInstanceEnricher.nameToLabel(QInstanceEnricher.inferNameFromBackendName(name()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Get instance by id
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static ScheduledJobType getById(String id)
|
||||
{
|
||||
if(id == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
for(ScheduledJobType value : ScheduledJobType.values())
|
||||
{
|
||||
if(value.name().equals(id))
|
||||
{
|
||||
return (value);
|
||||
}
|
||||
}
|
||||
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for id
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getId()
|
||||
{
|
||||
return name();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for label
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getLabel()
|
||||
{
|
||||
return label;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getPossibleValueId()
|
||||
{
|
||||
return name();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getPossibleValueLabel()
|
||||
{
|
||||
return (label);
|
||||
}
|
||||
|
||||
}
|
@ -1,219 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.scheduledjobs;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.ChildRecordListRenderer;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
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.joins.JoinOn;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.PermissionLevel;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
import com.kingsrook.qqq.backend.core.model.scheduledjobs.customizers.ScheduledJobTableCustomizer;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ScheduledJobsMetaDataProvider
|
||||
{
|
||||
private static final String JOB_PARAMETER_JOIN_NAME = QJoinMetaData.makeInferredJoinName(ScheduledJob.TABLE_NAME, ScheduledJobParameter.TABLE_NAME);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void defineAll(QInstance instance, String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
|
||||
{
|
||||
defineStandardTables(instance, backendName, backendDetailEnricher);
|
||||
instance.addPossibleValueSource(QPossibleValueSource.newForTable(ScheduledJob.TABLE_NAME));
|
||||
instance.addPossibleValueSource(QPossibleValueSource.newForEnum(ScheduledJobType.NAME, ScheduledJobType.values()));
|
||||
instance.addPossibleValueSource(defineSchedulersPossibleValueSource());
|
||||
defineStandardJoins(instance);
|
||||
defineStandardWidgets(instance);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void defineStandardWidgets(QInstance instance)
|
||||
{
|
||||
QJoinMetaData join = instance.getJoin(JOB_PARAMETER_JOIN_NAME);
|
||||
instance.addWidget(ChildRecordListRenderer.widgetMetaDataBuilder(join)
|
||||
.withCanAddChildRecord(true)
|
||||
.withManageAssociationName(ScheduledJobParameter.TABLE_NAME)
|
||||
.withLabel("Parameters")
|
||||
.getWidgetMetaData()
|
||||
.withPermissionRules(new QPermissionRules().withLevel(PermissionLevel.NOT_PROTECTED)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void defineStandardJoins(QInstance instance)
|
||||
{
|
||||
instance.addJoin(new QJoinMetaData()
|
||||
.withType(JoinType.ONE_TO_MANY)
|
||||
.withLeftTable(ScheduledJob.TABLE_NAME)
|
||||
.withRightTable(ScheduledJobParameter.TABLE_NAME)
|
||||
.withJoinOn(new JoinOn("id", "scheduledJobId"))
|
||||
.withOrderBy(new QFilterOrderBy("id"))
|
||||
.withInferredName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void defineStandardTables(QInstance instance, String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
|
||||
{
|
||||
for(QTableMetaData tableMetaData : defineStandardTables(backendName, backendDetailEnricher))
|
||||
{
|
||||
instance.addTable(tableMetaData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private List<QTableMetaData> defineStandardTables(String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
|
||||
{
|
||||
List<QTableMetaData> rs = new ArrayList<>();
|
||||
rs.add(enrich(backendDetailEnricher, defineScheduledJobTable(backendName)));
|
||||
rs.add(enrich(backendDetailEnricher, defineScheduledJobParameterTable(backendName)));
|
||||
return (rs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QTableMetaData enrich(Consumer<QTableMetaData> backendDetailEnricher, QTableMetaData table)
|
||||
{
|
||||
if(backendDetailEnricher != null)
|
||||
{
|
||||
backendDetailEnricher.accept(table);
|
||||
}
|
||||
return (table);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QTableMetaData defineStandardTable(String backendName, String name, Class<? extends QRecordEntity> fieldsFromEntity) throws QException
|
||||
{
|
||||
return new QTableMetaData()
|
||||
.withName(name)
|
||||
.withBackendName(backendName)
|
||||
.withPrimaryKeyField("id")
|
||||
.withFieldsFromEntity(fieldsFromEntity);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QTableMetaData defineScheduledJobTable(String backendName) throws QException
|
||||
{
|
||||
QTableMetaData tableMetaData = defineStandardTable(backendName, ScheduledJob.TABLE_NAME, ScheduledJob.class)
|
||||
.withRecordLabelFormat("%s")
|
||||
.withRecordLabelFields("label")
|
||||
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "label", "description")))
|
||||
.withSection(new QFieldSection("schedule", new QIcon().withName("alarm"), Tier.T2, List.of("cronExpression", "cronTimeZoneId", "repeatSeconds")))
|
||||
.withSection(new QFieldSection("settings", new QIcon().withName("tune"), Tier.T2, List.of("type", "isActive", "schedulerName")))
|
||||
.withSection(new QFieldSection("parameters", new QIcon().withName("list"), Tier.T2).withWidgetName(JOB_PARAMETER_JOIN_NAME))
|
||||
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
|
||||
|
||||
QCodeReference customizerReference = new QCodeReference(ScheduledJobTableCustomizer.class);
|
||||
tableMetaData.withCustomizer(TableCustomizers.PRE_INSERT_RECORD, customizerReference);
|
||||
tableMetaData.withCustomizer(TableCustomizers.POST_INSERT_RECORD, customizerReference);
|
||||
tableMetaData.withCustomizer(TableCustomizers.POST_UPDATE_RECORD, customizerReference);
|
||||
tableMetaData.withCustomizer(TableCustomizers.PRE_UPDATE_RECORD, customizerReference);
|
||||
tableMetaData.withCustomizer(TableCustomizers.POST_DELETE_RECORD, customizerReference);
|
||||
|
||||
tableMetaData.withAssociation(new Association()
|
||||
.withName(ScheduledJobParameter.TABLE_NAME)
|
||||
.withAssociatedTableName(ScheduledJobParameter.TABLE_NAME)
|
||||
.withJoinName(JOB_PARAMETER_JOIN_NAME));
|
||||
|
||||
return (tableMetaData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QTableMetaData defineScheduledJobParameterTable(String backendName) throws QException
|
||||
{
|
||||
QTableMetaData tableMetaData = defineStandardTable(backendName, ScheduledJobParameter.TABLE_NAME, ScheduledJobParameter.class)
|
||||
.withRecordLabelFormat("%s - %s")
|
||||
.withRecordLabelFields("scheduledJobId", "key")
|
||||
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "scheduledJobId", "key", "value")))
|
||||
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
|
||||
|
||||
return (tableMetaData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QPossibleValueSource defineSchedulersPossibleValueSource()
|
||||
{
|
||||
return (new QPossibleValueSource()
|
||||
.withName(SchedulersPossibleValueSource.NAME)
|
||||
.withType(QPossibleValueSourceType.CUSTOM)
|
||||
.withCustomCodeReference(new QCodeReference(SchedulersPossibleValueSource.class)));
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.scheduledjobs;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QSchedulerMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class SchedulersPossibleValueSource implements QCustomPossibleValueProvider<String>
|
||||
{
|
||||
public static final String NAME = "schedulers";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QPossibleValue<String> getPossibleValue(Serializable idValue)
|
||||
{
|
||||
QSchedulerMetaData scheduler = QContext.getQInstance().getScheduler(String.valueOf(idValue));
|
||||
if(scheduler != null)
|
||||
{
|
||||
return schedulerToPossibleValue(scheduler);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QPossibleValue<String>> search(SearchPossibleValueSourceInput input) throws QException
|
||||
{
|
||||
List<QPossibleValue<String>> rs = new ArrayList<>();
|
||||
for(QSchedulerMetaData scheduler : CollectionUtils.nonNullMap(QContext.getQInstance().getSchedulers()).values())
|
||||
{
|
||||
rs.add(schedulerToPossibleValue(scheduler));
|
||||
}
|
||||
return rs;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static QPossibleValue<String> schedulerToPossibleValue(QSchedulerMetaData scheduler)
|
||||
{
|
||||
return new QPossibleValue<>(scheduler.getName(), scheduler.getName());
|
||||
}
|
||||
|
||||
}
|
@ -1,278 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.scheduledjobs.customizers;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJob;
|
||||
import com.kingsrook.qqq.backend.core.model.statusmessages.BadInputStatusMessage;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.QScheduleManager;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ScheduledJobTableCustomizer implements TableCustomizerInterface
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException
|
||||
{
|
||||
validateConditionalFields(records);
|
||||
return (records);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> postInsert(InsertInput insertInput, List<QRecord> records) throws QException
|
||||
{
|
||||
scheduleJobsForRecordList(records);
|
||||
return (records);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> preUpdate(UpdateInput updateInput, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException
|
||||
{
|
||||
validateConditionalFields(records);
|
||||
|
||||
if(isPreview || oldRecordList.isEmpty())
|
||||
{
|
||||
return (records);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// refresh the old-records w/ versions that have associations - so we can use those in the post-update to property unschedule things //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
Map<Serializable, QRecord> freshOldRecordsWithAssociationsMap = CollectionUtils.recordsToMap(freshlyQueryForRecordsWithAssociations(oldRecordList.get()), "id");
|
||||
ListIterator<QRecord> iterator = oldRecordList.get().listIterator();
|
||||
while(iterator.hasNext())
|
||||
{
|
||||
QRecord record = iterator.next();
|
||||
QRecord freshRecord = freshOldRecordsWithAssociationsMap.get(record.getValue("id"));
|
||||
if(freshRecord != null)
|
||||
{
|
||||
iterator.set(freshRecord);
|
||||
}
|
||||
}
|
||||
|
||||
return (records);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void validateConditionalFields(List<QRecord> records)
|
||||
{
|
||||
for(QRecord record : records)
|
||||
{
|
||||
if(StringUtils.hasContent(record.getValueString("cronExpression")))
|
||||
{
|
||||
if(!StringUtils.hasContent(record.getValueString("cronTimeZoneId")))
|
||||
{
|
||||
record.addError(new BadInputStatusMessage("If a Cron Expression is given, then a Cron Time Zone Id is required."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> postUpdate(UpdateInput updateInput, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
|
||||
{
|
||||
if(oldRecordList.isPresent())
|
||||
{
|
||||
Set<Integer> idsWithErrors = getRecordIdsWithErrors(records);
|
||||
unscheduleJobsForRecordList(oldRecordList.get(), idsWithErrors);
|
||||
}
|
||||
|
||||
scheduleJobsForRecordList(records);
|
||||
|
||||
return (records);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static Set<Integer> getRecordIdsWithErrors(List<QRecord> records)
|
||||
{
|
||||
return records.stream()
|
||||
.filter(r -> !recordHasErrors().test(r))
|
||||
.map(r -> r.getValueInteger("id"))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> postDelete(DeleteInput deleteInput, List<QRecord> records) throws QException
|
||||
{
|
||||
Set<Integer> idsWithErrors = getRecordIdsWithErrors(records);
|
||||
unscheduleJobsForRecordList(records, idsWithErrors);
|
||||
return (records);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void scheduleJobsForRecordList(List<QRecord> records)
|
||||
{
|
||||
List<QRecord> recordsWithoutErrors = records.stream().filter(recordHasErrors()).toList();
|
||||
if(CollectionUtils.nullSafeIsEmpty(recordsWithoutErrors))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
List<QRecord> freshRecordListWithAssociations = freshlyQueryForRecordsWithAssociations(recordsWithoutErrors);
|
||||
|
||||
QScheduleManager scheduleManager = QScheduleManager.getInstance();
|
||||
for(QRecord record : freshRecordListWithAssociations)
|
||||
{
|
||||
try
|
||||
{
|
||||
scheduleManager.setupScheduledJob(new ScheduledJob(record));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.info("Caught exception while scheduling a job in post-action", e, logPair("id", record.getValue("id")));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error scheduling jobs in post-action", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static Predicate<QRecord> recordHasErrors()
|
||||
{
|
||||
return r -> CollectionUtils.nullSafeIsEmpty(r.getErrors());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static List<QRecord> freshlyQueryForRecordsWithAssociations(List<QRecord> records) throws QException
|
||||
{
|
||||
List<Integer> idList = records.stream().map(r -> r.getValueInteger("id")).toList();
|
||||
|
||||
return new QueryAction().execute(new QueryInput(ScheduledJob.TABLE_NAME)
|
||||
.withIncludeAssociations(true)
|
||||
.withFilter(new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.IN, idList))))
|
||||
.getRecords();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void unscheduleJobsForRecordList(List<QRecord> oldRecords, Set<Integer> exceptIdsWithErrors)
|
||||
{
|
||||
try
|
||||
{
|
||||
QScheduleManager scheduleManager = QScheduleManager.getInstance();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// for un-schedule - use the old records as they are - don't re-query them (they may not exist anymore!) //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
for(QRecord record : oldRecords)
|
||||
{
|
||||
try
|
||||
{
|
||||
ScheduledJob scheduledJob = new ScheduledJob(record);
|
||||
|
||||
if(exceptIdsWithErrors.contains(scheduledJob.getId()))
|
||||
{
|
||||
LOG.info("Will not unschedule the job for a record that had an error", logPair("id", scheduledJob.getId()));
|
||||
continue;
|
||||
}
|
||||
|
||||
scheduleManager.unscheduleScheduledJob(scheduledJob);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.info("Caught exception while scheduling a job in post-action", e, logPair("id", record.getValue("id")));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error scheduling jobs in post-action", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -372,7 +372,7 @@ public class MemoryRecordStore
|
||||
/////////////////////////////////////////////////
|
||||
// set the next serial in the record if needed //
|
||||
/////////////////////////////////////////////////
|
||||
if(recordToInsert.getValue(primaryKeyField.getName()) == null && (primaryKeyField.getType().equals(QFieldType.INTEGER) || primaryKeyField.getType().equals(QFieldType.LONG)))
|
||||
if(recordToInsert.getValue(primaryKeyField.getName()) == null && primaryKeyField.getType().equals(QFieldType.INTEGER))
|
||||
{
|
||||
recordToInsert.setValue(primaryKeyField.getName(), nextSerial++);
|
||||
}
|
||||
@ -384,13 +384,6 @@ public class MemoryRecordStore
|
||||
{
|
||||
nextSerial = recordToInsert.getValueInteger(primaryKeyField.getName()) + 1;
|
||||
}
|
||||
else if(primaryKeyField.getType().equals(QFieldType.LONG) && recordToInsert.getValueLong(primaryKeyField.getName()) > nextSerial)
|
||||
{
|
||||
//////////////////////////////////////
|
||||
// todo - mmm, could overflow here? //
|
||||
//////////////////////////////////////
|
||||
nextSerial = recordToInsert.getValueInteger(primaryKeyField.getName()) + 1;
|
||||
}
|
||||
|
||||
tableData.put(recordToInsert.getValue(primaryKeyField.getName()), recordToInsert);
|
||||
if(returnInsertedRecords)
|
||||
@ -780,7 +773,7 @@ public class MemoryRecordStore
|
||||
{
|
||||
// todo - joins probably?
|
||||
QFieldMetaData field = table.getField(fieldName);
|
||||
if((field.getType().equals(QFieldType.INTEGER) || field.getType().equals(QFieldType.LONG)) && (operator.equals(AggregateOperator.AVG)))
|
||||
if(field.getType().equals(QFieldType.INTEGER) && (operator.equals(AggregateOperator.AVG)))
|
||||
{
|
||||
fieldType = QFieldType.DECIMAL;
|
||||
}
|
||||
@ -816,10 +809,6 @@ public class MemoryRecordStore
|
||||
.filter(r -> r.getValue(fieldName) != null)
|
||||
.mapToInt(r -> r.getValueInteger(fieldName))
|
||||
.sum();
|
||||
case LONG -> records.stream()
|
||||
.filter(r -> r.getValue(fieldName) != null)
|
||||
.mapToLong(r -> r.getValueLong(fieldName))
|
||||
.sum();
|
||||
case DECIMAL -> records.stream()
|
||||
.filter(r -> r.getValue(fieldName) != null)
|
||||
.map(r -> r.getValueBigDecimal(fieldName))
|
||||
@ -834,11 +823,6 @@ public class MemoryRecordStore
|
||||
.mapToInt(r -> r.getValueInteger(fieldName))
|
||||
.min()
|
||||
.stream().boxed().findFirst().orElse(null);
|
||||
case LONG -> records.stream()
|
||||
.filter(r -> r.getValue(fieldName) != null)
|
||||
.mapToLong(r -> r.getValueLong(fieldName))
|
||||
.min()
|
||||
.stream().boxed().findFirst().orElse(null);
|
||||
case DECIMAL, STRING, DATE, DATE_TIME ->
|
||||
{
|
||||
Optional<Serializable> serializable = records.stream()
|
||||
@ -855,12 +839,7 @@ public class MemoryRecordStore
|
||||
{
|
||||
case INTEGER -> records.stream()
|
||||
.filter(r -> r.getValue(fieldName) != null)
|
||||
.mapToLong(r -> r.getValueInteger(fieldName))
|
||||
.max()
|
||||
.stream().boxed().findFirst().orElse(null);
|
||||
case LONG -> records.stream()
|
||||
.filter(r -> r.getValue(fieldName) != null)
|
||||
.mapToLong(r -> r.getValueLong(fieldName))
|
||||
.mapToInt(r -> r.getValueInteger(fieldName))
|
||||
.max()
|
||||
.stream().boxed().findFirst().orElse(null);
|
||||
case DECIMAL, STRING, DATE, DATE_TIME ->
|
||||
@ -882,11 +861,6 @@ public class MemoryRecordStore
|
||||
.mapToInt(r -> r.getValueInteger(fieldName))
|
||||
.average()
|
||||
.stream().boxed().findFirst().orElse(null);
|
||||
case LONG -> records.stream()
|
||||
.filter(r -> r.getValue(fieldName) != null)
|
||||
.mapToLong(r -> r.getValueLong(fieldName))
|
||||
.average()
|
||||
.stream().boxed().findFirst().orElse(null);
|
||||
case DECIMAL -> records.stream()
|
||||
.filter(r -> r.getValue(fieldName) != null)
|
||||
.mapToDouble(r -> r.getValueBigDecimal(fieldName).doubleValue())
|
||||
|
@ -103,7 +103,6 @@ public class MockQueryAction implements QueryInterface
|
||||
{
|
||||
case STRING -> UUID.randomUUID().toString();
|
||||
case INTEGER -> 42;
|
||||
case LONG -> 42L;
|
||||
case DECIMAL -> new BigDecimal("3.14159");
|
||||
case DATE -> LocalDate.of(1970, Month.JANUARY, 1);
|
||||
case DATE_TIME -> LocalDateTime.of(1970, Month.JANUARY, 1, 0, 0);
|
||||
|
@ -55,7 +55,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.DynamicDefaultValueBehavior;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
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.QComponentType;
|
||||
@ -130,7 +129,6 @@ public class HealBadRecordAutomationStatusesProcessStep implements BackendStep,
|
||||
|
||||
QProcessMetaData processMetaData = new QProcessMetaData()
|
||||
.withName(NAME)
|
||||
.withIcon(new QIcon().withName("healing"))
|
||||
.withStepList(List.of(
|
||||
new QFrontendStepMetaData()
|
||||
.withName("input")
|
||||
|
@ -36,7 +36,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.automation.QAutomationProvi
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
|
||||
@ -72,7 +71,6 @@ public class RunTableAutomationsProcessStep implements BackendStep, MetaDataProd
|
||||
{
|
||||
QProcessMetaData processMetaData = new QProcessMetaData()
|
||||
.withName(NAME)
|
||||
.withIcon(new QIcon().withName("directions_run"))
|
||||
.withStepList(List.of(
|
||||
new QFrontendStepMetaData()
|
||||
.withName("input")
|
||||
|
@ -34,7 +34,6 @@ import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreInsertCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreInsertCustomizer.WhenToRun;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.UniqueKeyHelper;
|
||||
@ -138,13 +137,15 @@ public class BulkInsertTransformStep extends AbstractTransformStep
|
||||
// we do this, in case it needs to, for example, adjust values that //
|
||||
// are part of a unique key //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
Optional<TableCustomizerInterface> preInsertCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.PRE_INSERT_RECORD.getRole());
|
||||
Optional<AbstractPreInsertCustomizer> preInsertCustomizer = QCodeLoader.getTableCustomizer(AbstractPreInsertCustomizer.class, table, TableCustomizers.PRE_INSERT_RECORD.getRole());
|
||||
if(preInsertCustomizer.isPresent())
|
||||
{
|
||||
AbstractPreInsertCustomizer.WhenToRun whenToRun = preInsertCustomizer.get().whenToRunPreInsert(insertInput, true);
|
||||
preInsertCustomizer.get().setInsertInput(insertInput);
|
||||
preInsertCustomizer.get().setIsPreview(true);
|
||||
AbstractPreInsertCustomizer.WhenToRun whenToRun = preInsertCustomizer.get().getWhenToRun();
|
||||
if(WhenToRun.BEFORE_ALL_VALIDATIONS.equals(whenToRun) || WhenToRun.BEFORE_UNIQUE_KEY_CHECKS.equals(whenToRun))
|
||||
{
|
||||
List<QRecord> recordsAfterCustomizer = preInsertCustomizer.get().preInsert(insertInput, runBackendStepInput.getRecords(), true);
|
||||
List<QRecord> recordsAfterCustomizer = preInsertCustomizer.get().apply(runBackendStepInput.getRecords());
|
||||
runBackendStepInput.setRecords(recordsAfterCustomizer);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -104,12 +104,12 @@ public class BaseStreamedETLStep
|
||||
*******************************************************************************/
|
||||
protected void moveReviewStepAfterValidateStep(RunBackendStepOutput runBackendStepOutput)
|
||||
{
|
||||
LOG.debug("Skipping to validation step");
|
||||
LOG.info("Skipping to validation step");
|
||||
ArrayList<String> stepList = new ArrayList<>(runBackendStepOutput.getProcessState().getStepList());
|
||||
LOG.trace("Step list pre: " + stepList);
|
||||
LOG.debug("Step list pre: " + stepList);
|
||||
stepList.removeIf(s -> s.equals(StreamedETLWithFrontendProcess.STEP_NAME_REVIEW));
|
||||
stepList.add(stepList.indexOf(StreamedETLWithFrontendProcess.STEP_NAME_VALIDATE) + 1, StreamedETLWithFrontendProcess.STEP_NAME_REVIEW);
|
||||
runBackendStepOutput.getProcessState().setStepList(stepList);
|
||||
LOG.trace("Step list post: " + stepList);
|
||||
LOG.debug("Step list post: " + stepList);
|
||||
}
|
||||
}
|
||||
|
@ -113,6 +113,10 @@ public class ExtractViaQueryStep extends AbstractExtractStep
|
||||
{
|
||||
queryInput.setShouldFetchHeavyFields(true);
|
||||
}
|
||||
if(runBackendStepInput.getValuePrimitiveBoolean(StreamedETLWithFrontendProcess.FIELD_INCLUDE_ASSOCIATIONS))
|
||||
{
|
||||
queryInput.setIncludeAssociations(true);
|
||||
}
|
||||
|
||||
customizeInputPreQuery(queryInput);
|
||||
|
||||
|
@ -136,7 +136,7 @@ public class StreamedETLExecuteStep extends BaseStreamedETLStep implements Backe
|
||||
asyncRecordPipeLoop.setMinRecordsToConsume(overrideRecordPipeCapacity);
|
||||
}
|
||||
|
||||
int recordCount = asyncRecordPipeLoop.run("StreamedETLExecute>Extract>" + runBackendStepInput.getProcessName(), null, recordPipe, (status) ->
|
||||
int recordCount = asyncRecordPipeLoop.run("StreamedETL>Execute>ExtractStep", null, recordPipe, (status) ->
|
||||
{
|
||||
extractStep.run(runBackendStepInput, runBackendStepOutput);
|
||||
return (runBackendStepOutput);
|
||||
|
@ -125,7 +125,7 @@ public class StreamedETLPreviewStep extends BaseStreamedETLStep implements Backe
|
||||
// }
|
||||
|
||||
List<QRecord> previewRecordList = new ArrayList<>();
|
||||
new AsyncRecordPipeLoop().run("StreamedETLPreview>Extract>" + runBackendStepInput.getProcessName(), PROCESS_OUTPUT_RECORD_LIST_LIMIT, recordPipe, (status) ->
|
||||
new AsyncRecordPipeLoop().run("StreamedETL>Preview>ExtractStep", PROCESS_OUTPUT_RECORD_LIST_LIMIT, recordPipe, (status) ->
|
||||
{
|
||||
runBackendStepInput.setAsyncJobCallback(status);
|
||||
extractStep.run(runBackendStepInput, runBackendStepOutput);
|
||||
|
@ -119,7 +119,7 @@ public class StreamedETLValidateStep extends BaseStreamedETLStep implements Back
|
||||
transformStep.preRun(runBackendStepInput, runBackendStepOutput);
|
||||
|
||||
List<QRecord> previewRecordList = new ArrayList<>();
|
||||
int recordCount = new AsyncRecordPipeLoop().run("StreamedETLValidate>Extract>" + runBackendStepInput.getProcessName(), null, recordPipe, (status) ->
|
||||
int recordCount = new AsyncRecordPipeLoop().run("StreamedETL>Preview>ValidateStep", null, recordPipe, (status) ->
|
||||
{
|
||||
extractStep.run(runBackendStepInput, runBackendStepOutput);
|
||||
return (runBackendStepOutput);
|
||||
|
@ -41,7 +41,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMet
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionOutputMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.VariantRunStrategy;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaData;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.basepull.BasepullConfiguration;
|
||||
|
||||
@ -84,6 +83,7 @@ public class StreamedETLWithFrontendProcess
|
||||
public static final String FIELD_RECORD_COUNT = "recordCount"; // Integer
|
||||
public static final String FIELD_DEFAULT_QUERY_FILTER = "defaultQueryFilter"; // QQueryFilter or String (json, of q QQueryFilter)
|
||||
public static final String FIELD_FETCH_HEAVY_FIELDS = "fetchHeavyFields"; // Boolean
|
||||
public static final String FIELD_INCLUDE_ASSOCIATIONS = "includeAssociations"; // Boolean
|
||||
|
||||
public static final String FIELD_SUPPORTS_FULL_VALIDATION = "supportsFullValidation"; // Boolean
|
||||
public static final String FIELD_DO_FULL_VALIDATION = "doFullValidation"; // Boolean
|
||||
@ -145,6 +145,7 @@ public class StreamedETLWithFrontendProcess
|
||||
.withCode(new QCodeReference(StreamedETLPreviewStep.class))
|
||||
.withInputData(new QFunctionInputMetaData()
|
||||
.withField(new QFieldMetaData(FIELD_SOURCE_TABLE, QFieldType.STRING).withDefaultValue(defaultFieldValues.get(FIELD_SOURCE_TABLE)))
|
||||
.withField(new QFieldMetaData(FIELD_INCLUDE_ASSOCIATIONS, QFieldType.BOOLEAN).withDefaultValue(defaultFieldValues.getOrDefault(FIELD_INCLUDE_ASSOCIATIONS, false)))
|
||||
.withField(new QFieldMetaData(FIELD_FETCH_HEAVY_FIELDS, QFieldType.BOOLEAN).withDefaultValue(defaultFieldValues.getOrDefault(FIELD_FETCH_HEAVY_FIELDS, false)))
|
||||
.withField(new QFieldMetaData(FIELD_DESTINATION_TABLE, QFieldType.STRING).withDefaultValue(defaultFieldValues.get(FIELD_DESTINATION_TABLE)))
|
||||
.withField(new QFieldMetaData(FIELD_SUPPORTS_FULL_VALIDATION, QFieldType.BOOLEAN).withDefaultValue(defaultFieldValues.getOrDefault(FIELD_SUPPORTS_FULL_VALIDATION, true)))
|
||||
@ -489,28 +490,5 @@ public class StreamedETLWithFrontendProcess
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Builder withVariantRunStrategy(VariantRunStrategy variantRunStrategy)
|
||||
{
|
||||
processMetaData.setVariantRunStrategy(variantRunStrategy);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Builder withVariantBackend(String variantBackend)
|
||||
{
|
||||
processMetaData.setVariantBackend(variantBackend);
|
||||
return (this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
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.model.metadata.processes.VariantRunStrategy;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaData;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.basepull.BasepullConfiguration;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.basepull.ExtractViaBasepullQueryStep;
|
||||
@ -249,28 +248,5 @@ public class TableSyncProcess
|
||||
super.withExtractStepClass(extractStepClass);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Builder withVariantRunStrategy(VariantRunStrategy variantRunStrategy)
|
||||
{
|
||||
processMetaData.setVariantRunStrategy(variantRunStrategy);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Builder withVariantBackend(String variantBackend)
|
||||
{
|
||||
processMetaData.setVariantBackend(variantBackend);
|
||||
return (this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,519 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.polling.PollingAutomationPerTableRunner;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.context.CapturedContext;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
|
||||
import com.kingsrook.qqq.backend.core.logging.LogPair;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
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.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.VariantRunStrategy;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QSchedulerMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.QTableAutomationDetails;
|
||||
import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJob;
|
||||
import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJobType;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.SchedulableType;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.identity.BasicSchedulableIdentity;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.identity.SchedulableIdentity;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.identity.SchedulableIdentityFactory;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.runner.SchedulableProcessRunner;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.runner.SchedulableRunner;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.runner.SchedulableSQSQueueRunner;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.runner.SchedulableTableAutomationsRunner;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QQQ service to manage scheduled jobs, using 1 or more Schedulers - implementations
|
||||
** of the QSchedulerInterface
|
||||
*******************************************************************************/
|
||||
public class QScheduleManager
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QScheduleManager.class);
|
||||
|
||||
private static QScheduleManager qScheduleManager = null;
|
||||
private final QInstance qInstance;
|
||||
private final Supplier<QSession> systemUserSessionSupplier;
|
||||
|
||||
private Map<String, QSchedulerInterface> schedulers = new HashMap<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton constructor
|
||||
*******************************************************************************/
|
||||
private QScheduleManager(QInstance qInstance, Supplier<QSession> systemUserSessionSupplier)
|
||||
{
|
||||
this.qInstance = qInstance;
|
||||
this.systemUserSessionSupplier = systemUserSessionSupplier;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton initiator - e.g., must be called to initially initialize the singleton
|
||||
** before anyone else calls getInstance (they'll get an error if they call that first).
|
||||
*******************************************************************************/
|
||||
public static QScheduleManager initInstance(QInstance qInstance, Supplier<QSession> systemUserSessionSupplier) throws QException
|
||||
{
|
||||
if(qScheduleManager == null)
|
||||
{
|
||||
qScheduleManager = new QScheduleManager(qInstance, systemUserSessionSupplier);
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// if the instance doesn't have any schedulable types defined, //
|
||||
// then go ahead and add the default set that qqq knows about //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
if(CollectionUtils.nullSafeIsEmpty(qInstance.getSchedulableTypes()))
|
||||
{
|
||||
defineDefaultSchedulableTypesInInstance(qInstance);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// initialize the scheduler(s) we're configured to use //
|
||||
// do this, even if we won't start them - so, for example, a web server can still be aware of schedules in the application //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
for(QSchedulerMetaData schedulerMetaData : CollectionUtils.nonNullMap(qInstance.getSchedulers()).values())
|
||||
{
|
||||
QSchedulerInterface scheduler = schedulerMetaData.initSchedulerInstance(qInstance, systemUserSessionSupplier);
|
||||
qScheduleManager.schedulers.put(schedulerMetaData.getName(), scheduler);
|
||||
}
|
||||
}
|
||||
return (qScheduleManager);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void defineDefaultSchedulableTypesInInstance(QInstance qInstance)
|
||||
{
|
||||
qInstance.addSchedulableType(new SchedulableType().withName(ScheduledJobType.PROCESS.getId()).withRunner(new QCodeReference(SchedulableProcessRunner.class)));
|
||||
qInstance.addSchedulableType(new SchedulableType().withName(ScheduledJobType.QUEUE_PROCESSOR.getId()).withRunner(new QCodeReference(SchedulableSQSQueueRunner.class)));
|
||||
qInstance.addSchedulableType(new SchedulableType().withName(ScheduledJobType.TABLE_AUTOMATIONS.getId()).withRunner(new QCodeReference(SchedulableTableAutomationsRunner.class)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton accessor
|
||||
*******************************************************************************/
|
||||
public static QScheduleManager getInstance()
|
||||
{
|
||||
if(qScheduleManager == null)
|
||||
{
|
||||
throw (new IllegalStateException("QScheduleManager singleton has not been init'ed (call initInstance)."));
|
||||
}
|
||||
return (qScheduleManager);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void start() throws QException
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// exit w/o starting schedulers, if schedule manager isn't enabled here //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
if(!new QMetaDataVariableInterpreter().getBooleanFromPropertyOrEnvironment("qqq.scheduleManager.enabled", "QQQ_SCHEDULE_MANAGER_ENABLED", true))
|
||||
{
|
||||
LOG.info("Not starting ScheduleManager per settings.");
|
||||
schedulers.values().forEach(s -> s.doNotStart());
|
||||
return;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ensure that everything which should be scheduled is scheduled, in the appropriate scheduler //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QContext.withTemporaryContext(new CapturedContext(qInstance, systemUserSessionSupplier.get()), () -> setupAllSchedules());
|
||||
|
||||
//////////////////////////
|
||||
// start each scheduler //
|
||||
//////////////////////////
|
||||
schedulers.values().forEach(s -> s.start());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void stop()
|
||||
{
|
||||
schedulers.values().forEach(s -> s.stop());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void stopAsync()
|
||||
{
|
||||
schedulers.values().forEach(s -> s.stopAsync());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setupAllSchedules() throws QException
|
||||
{
|
||||
/////////////////////////////////////////////
|
||||
// read dynamic schedules //
|
||||
// e.g., user-scheduled processes, reports //
|
||||
/////////////////////////////////////////////
|
||||
List<ScheduledJob> scheduledJobList = null;
|
||||
try
|
||||
{
|
||||
if(QContext.getQInstance().getTables().containsKey(ScheduledJob.TABLE_NAME))
|
||||
{
|
||||
scheduledJobList = new QueryAction()
|
||||
.execute(new QueryInput(ScheduledJob.TABLE_NAME)
|
||||
.withIncludeAssociations(true))
|
||||
.getRecordEntities(ScheduledJob.class);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QRuntimeException("Failed to query for scheduled jobs - will not set up scheduler!", e));
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// let the schedulers know we're starting this process //
|
||||
/////////////////////////////////////////////////////////
|
||||
schedulers.values().forEach(s -> s.startOfSetupSchedules());
|
||||
|
||||
/////////////////////////
|
||||
// schedule all queues //
|
||||
/////////////////////////
|
||||
for(QQueueMetaData queue : qInstance.getQueues().values())
|
||||
{
|
||||
if(queue.getSchedule() != null)
|
||||
{
|
||||
setupQueue(queue);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////
|
||||
// schedule all tables w/ automations //
|
||||
////////////////////////////////////////
|
||||
for(QTableMetaData table : qInstance.getTables().values())
|
||||
{
|
||||
QTableAutomationDetails automationDetails = table.getAutomationDetails();
|
||||
if(automationDetails != null && automationDetails.getSchedule() != null)
|
||||
{
|
||||
setupTableAutomations(table);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////
|
||||
// schedule all processes that need it //
|
||||
/////////////////////////////////////////
|
||||
for(QProcessMetaData process : qInstance.getProcesses().values())
|
||||
{
|
||||
if(process.getSchedule() != null)
|
||||
{
|
||||
setupProcess(process);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// todo- before, or after meta-datas? //
|
||||
// like quartz, it'd just re-schedule if a dupe - but, should we do our own dupe checking? //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
for(ScheduledJob scheduledJob : CollectionUtils.nonNullList(scheduledJobList))
|
||||
{
|
||||
try
|
||||
{
|
||||
setupScheduledJob(scheduledJob);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.info("Caught exception while scheduling a job", e, logPair("id", scheduledJob.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
// let the schedulers know we're done with this process //
|
||||
//////////////////////////////////////////////////////////
|
||||
schedulers.values().forEach(s -> s.endOfSetupSchedules());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setupScheduledJob(ScheduledJob scheduledJob) throws QException
|
||||
{
|
||||
BasicSchedulableIdentity schedulableIdentity = SchedulableIdentityFactory.of(scheduledJob);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// non-active jobs should be deleted from the scheduler. they get re-added //
|
||||
// if they get re-activated. but we don't want to rely on (e.g., for quartz) //
|
||||
// the paused state to be drive by is-active. else, devops-pause & unpause //
|
||||
// operations would clobber scheduled-job record facts //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
if(!scheduledJob.getIsActive())
|
||||
{
|
||||
unscheduleScheduledJob(scheduledJob);
|
||||
return;
|
||||
}
|
||||
|
||||
String exceptionSuffix = "in scheduledJob [" + scheduledJob.getId() + "]";
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// setup schedule meta-data object based on schedule data in the scheduled job - throwing if not well populated //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(scheduledJob.getRepeatSeconds() == null && !StringUtils.hasContent(scheduledJob.getCronExpression()))
|
||||
{
|
||||
throw (new QException("Missing a schedule (cronString or repeatSeconds) " + exceptionSuffix));
|
||||
}
|
||||
|
||||
QScheduleMetaData scheduleMetaData = new QScheduleMetaData();
|
||||
scheduleMetaData.setCronExpression(scheduledJob.getCronExpression());
|
||||
scheduleMetaData.setCronTimeZoneId(scheduledJob.getCronTimeZoneId());
|
||||
scheduleMetaData.setRepeatSeconds(scheduledJob.getRepeatSeconds());
|
||||
|
||||
/////////////////////////////////
|
||||
// get & validate the job type //
|
||||
/////////////////////////////////
|
||||
if(!StringUtils.hasContent(scheduledJob.getType()))
|
||||
{
|
||||
throw (new QException("Missing a type " + exceptionSuffix));
|
||||
}
|
||||
|
||||
ScheduledJobType scheduledJobType = ScheduledJobType.getById(scheduledJob.getType());
|
||||
if(scheduledJobType == null)
|
||||
{
|
||||
throw (new QException("Unrecognized type [" + scheduledJob.getType() + "] " + exceptionSuffix));
|
||||
}
|
||||
|
||||
QSchedulerInterface scheduler = getScheduler(scheduledJob.getSchedulerName());
|
||||
Map<String, Serializable> paramMap = new HashMap<>(scheduledJob.getJobParametersMap());
|
||||
|
||||
SchedulableType schedulableType = qInstance.getSchedulableType(scheduledJob.getType());
|
||||
|
||||
SchedulableRunner runner = QCodeLoader.getAdHoc(SchedulableRunner.class, schedulableType.getRunner());
|
||||
runner.validateParams(schedulableIdentity, new HashMap<>(paramMap));
|
||||
|
||||
scheduler.setupSchedulable(schedulableIdentity, schedulableType, paramMap, scheduleMetaData, true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void unscheduleAll()
|
||||
{
|
||||
schedulers.values().forEach(s ->
|
||||
{
|
||||
try
|
||||
{
|
||||
s.unscheduleAll();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error unscheduling everything in scheduler " + s, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void unscheduleScheduledJob(ScheduledJob scheduledJob) throws QException
|
||||
{
|
||||
QSchedulerInterface scheduler = getScheduler(scheduledJob.getSchedulerName());
|
||||
|
||||
BasicSchedulableIdentity schedulableIdentity = SchedulableIdentityFactory.of(scheduledJob);
|
||||
SchedulableType schedulableType = qInstance.getSchedulableType(scheduledJob.getType());
|
||||
|
||||
scheduler.unscheduleSchedulable(schedulableIdentity, schedulableType);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void setupProcess(QProcessMetaData process) throws QException
|
||||
{
|
||||
BasicSchedulableIdentity schedulableIdentity = SchedulableIdentityFactory.of(process);
|
||||
QSchedulerInterface scheduler = getScheduler(process.getSchedule().getSchedulerName());
|
||||
boolean allowedToStart = SchedulerUtils.allowedToStart(process.getName());
|
||||
|
||||
Map<String, String> paramMap = new HashMap<>();
|
||||
paramMap.put("processName", process.getName());
|
||||
|
||||
SchedulableType schedulableType = qInstance.getSchedulableType(ScheduledJobType.PROCESS.getId());
|
||||
|
||||
if(process.getVariantBackend() == null || VariantRunStrategy.SERIAL.equals(process.getVariantRunStrategy()))
|
||||
{
|
||||
///////////////////////////////////////////////
|
||||
// if no variants, or variant is serial mode //
|
||||
///////////////////////////////////////////////
|
||||
scheduler.setupSchedulable(schedulableIdentity, schedulableType, new HashMap<>(paramMap), process.getSchedule(), allowedToStart);
|
||||
}
|
||||
else if(VariantRunStrategy.PARALLEL.equals(process.getVariantRunStrategy()))
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if this a "parallel", which for example means we want to have a thread for each backend variant //
|
||||
// running at the same time, get the variant records and schedule each separately //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QBackendMetaData backendMetaData = qInstance.getBackend(process.getVariantBackend());
|
||||
for(QRecord qRecord : CollectionUtils.nonNullList(SchedulerUtils.getBackendVariantFilteredRecords(process)))
|
||||
{
|
||||
try
|
||||
{
|
||||
HashMap<String, Serializable> parameters = new HashMap<>(paramMap);
|
||||
HashMap<String, Serializable> variantMap = new HashMap<>(Map.of(backendMetaData.getVariantOptionsTableTypeValue(), qRecord.getValue(backendMetaData.getVariantOptionsTableIdField())));
|
||||
parameters.put("backendVariantData", variantMap);
|
||||
|
||||
String identity = schedulableIdentity.getIdentity() + ";" + backendMetaData.getVariantOptionsTableTypeValue() + "=" + qRecord.getValue(backendMetaData.getVariantOptionsTableIdField());
|
||||
String description = schedulableIdentity.getDescription() + " for variant: " + backendMetaData.getVariantOptionsTableTypeValue() + "=" + qRecord.getValue(backendMetaData.getVariantOptionsTableIdField());
|
||||
|
||||
BasicSchedulableIdentity variantIdentity = new BasicSchedulableIdentity(identity, description);
|
||||
|
||||
scheduler.setupSchedulable(variantIdentity, schedulableType, parameters, process.getSchedule(), allowedToStart);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("An error starting process [" + process.getLabel() + "], with backend variant data.", e, new LogPair("variantQRecord", qRecord));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.error("Unsupported Schedule Run Strategy [" + process.getVariantRunStrategy() + "] was provided.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void setupTableAutomations(QTableMetaData table) throws QException
|
||||
{
|
||||
SchedulableType schedulableType = qInstance.getSchedulableType(ScheduledJobType.TABLE_AUTOMATIONS.getId());
|
||||
QTableAutomationDetails automationDetails = table.getAutomationDetails();
|
||||
QSchedulerInterface scheduler = getScheduler(automationDetails.getSchedule().getSchedulerName());
|
||||
|
||||
List<PollingAutomationPerTableRunner.TableActionsInterface> tableActionList = PollingAutomationPerTableRunner.getTableActions(qInstance, automationDetails.getProviderName())
|
||||
.stream().filter(ta -> ta.tableName().equals(table.getName()))
|
||||
.toList();
|
||||
|
||||
for(PollingAutomationPerTableRunner.TableActionsInterface tableActions : tableActionList)
|
||||
{
|
||||
SchedulableIdentity schedulableIdentity = SchedulableIdentityFactory.of(tableActions);
|
||||
boolean allowedToStart = SchedulerUtils.allowedToStart(table.getName());
|
||||
|
||||
Map<String, String> paramMap = new HashMap<>();
|
||||
paramMap.put("tableName", tableActions.tableName());
|
||||
paramMap.put("automationStatus", tableActions.status().name());
|
||||
scheduler.setupSchedulable(schedulableIdentity, schedulableType, new HashMap<>(paramMap), automationDetails.getSchedule(), allowedToStart);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void setupQueue(QQueueMetaData queue) throws QException
|
||||
{
|
||||
SchedulableIdentity schedulableIdentity = SchedulableIdentityFactory.of(queue);
|
||||
QSchedulerInterface scheduler = getScheduler(queue.getSchedule().getSchedulerName());
|
||||
SchedulableType schedulableType = qInstance.getSchedulableType(ScheduledJobType.QUEUE_PROCESSOR.getId());
|
||||
boolean allowedToStart = SchedulerUtils.allowedToStart(queue.getName());
|
||||
|
||||
Map<String, String> paramMap = new HashMap<>();
|
||||
paramMap.put("queueName", queue.getName());
|
||||
scheduler.setupSchedulable(schedulableIdentity, schedulableType, new HashMap<>(paramMap), queue.getSchedule(), allowedToStart);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QSchedulerInterface getScheduler(String schedulerName) throws QException
|
||||
{
|
||||
if(!StringUtils.hasContent(schedulerName))
|
||||
{
|
||||
throw (new QException("Scheduler name was not given (and the concept of a default scheduler does not exist at this time)."));
|
||||
}
|
||||
|
||||
QSchedulerInterface scheduler = schedulers.get(schedulerName);
|
||||
if(scheduler == null)
|
||||
{
|
||||
throw (new QException("Unrecognized schedulerName [" + schedulerName + "]"));
|
||||
}
|
||||
|
||||
return (scheduler);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** reset the singleton instance (to null); clear the map of schedulers.
|
||||
** Not clear it's ever useful to call in main-code - but can be used for tests.
|
||||
*******************************************************************************/
|
||||
public void unInit()
|
||||
{
|
||||
qScheduleManager = null;
|
||||
schedulers.values().forEach(s -> s.unInit());
|
||||
schedulers.clear();
|
||||
}
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaData;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.SchedulableType;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.identity.SchedulableIdentity;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public interface QSchedulerInterface
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
String getSchedulerName();
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void start();
|
||||
|
||||
/*******************************************************************************
|
||||
** called to indicate that the schedule manager is past its startup routine,
|
||||
** but that the schedule should not actually be running in this process.
|
||||
*******************************************************************************/
|
||||
default void doNotStart()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void setupSchedulable(SchedulableIdentity schedulableIdentity, SchedulableType schedulableType, Map<String, Serializable> parameters, QScheduleMetaData schedule, boolean allowedToStart);
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void unscheduleSchedulable(SchedulableIdentity schedulableIdentity, SchedulableType schedulableType);
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void unscheduleAll() throws QException;
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void stopAsync();
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void stop();
|
||||
|
||||
/*******************************************************************************
|
||||
** Handle a whole shutdown of the scheduler system (e.g., between unit tests).
|
||||
*******************************************************************************/
|
||||
default void unInit()
|
||||
{
|
||||
/////////////////////
|
||||
// noop by default //
|
||||
/////////////////////
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** let the scheduler know when the schedule manager is at the start of setting up schedules.
|
||||
*******************************************************************************/
|
||||
default void startOfSetupSchedules()
|
||||
{
|
||||
/////////////////////
|
||||
// noop by default //
|
||||
/////////////////////
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** let the scheduler know when the schedule manager is at the end of setting up schedules.
|
||||
*******************************************************************************/
|
||||
default void endOfSetupSchedules()
|
||||
{
|
||||
/////////////////////
|
||||
// noop by default //
|
||||
/////////////////////
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,502 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.polling.PollingAutomationPerTableRunner;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.queues.SQSQueuePoller;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
|
||||
import com.kingsrook.qqq.backend.core.logging.LogPair;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.automation.QAutomationProviderMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueProviderMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData;
|
||||
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.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QQQ Service (Singleton) that starts up repeating, scheduled jobs within QQQ.
|
||||
**
|
||||
** These include:
|
||||
** - Automation providers (which require polling)
|
||||
** - Queue pollers
|
||||
** - Scheduled processes.
|
||||
**
|
||||
** All of these jobs run using a "system session" - as defined by the sessionSupplier.
|
||||
*******************************************************************************/
|
||||
public class ScheduleManager
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(ScheduleManager.class);
|
||||
|
||||
private static ScheduleManager scheduleManager = null;
|
||||
private final QInstance qInstance;
|
||||
|
||||
protected Supplier<QSession> sessionSupplier;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// for jobs that don't define a delay index, auto-stagger them, using this counter //
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
private int delayIndex = 0;
|
||||
|
||||
private List<StandardScheduledExecutor> executors = new ArrayList<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton constructor
|
||||
*******************************************************************************/
|
||||
private ScheduleManager(QInstance qInstance)
|
||||
{
|
||||
this.qInstance = qInstance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton accessor
|
||||
*******************************************************************************/
|
||||
public static ScheduleManager getInstance(QInstance qInstance)
|
||||
{
|
||||
if(scheduleManager == null)
|
||||
{
|
||||
scheduleManager = new ScheduleManager(qInstance);
|
||||
}
|
||||
return (scheduleManager);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void start()
|
||||
{
|
||||
if(!new QMetaDataVariableInterpreter().getBooleanFromPropertyOrEnvironment("qqq.scheduleManager.enabled", "QQQ_SCHEDULE_MANAGER_ENABLED", true))
|
||||
{
|
||||
LOG.info("Not starting ScheduleManager per settings.");
|
||||
return;
|
||||
}
|
||||
|
||||
boolean needToClearContext = false;
|
||||
try
|
||||
{
|
||||
if(QContext.getQInstance() == null)
|
||||
{
|
||||
needToClearContext = true;
|
||||
QContext.init(qInstance, sessionSupplier.get());
|
||||
}
|
||||
|
||||
for(QQueueProviderMetaData queueProvider : qInstance.getQueueProviders().values())
|
||||
{
|
||||
startQueueProvider(queueProvider);
|
||||
}
|
||||
|
||||
for(QAutomationProviderMetaData automationProvider : qInstance.getAutomationProviders().values())
|
||||
{
|
||||
startAutomationProviderPerTable(automationProvider);
|
||||
}
|
||||
|
||||
for(QProcessMetaData process : qInstance.getProcesses().values())
|
||||
{
|
||||
if(process.getSchedule() != null && allowedToStart(process.getName()))
|
||||
{
|
||||
QScheduleMetaData scheduleMetaData = process.getSchedule();
|
||||
if(process.getSchedule().getVariantBackend() == null || QScheduleMetaData.RunStrategy.SERIAL.equals(process.getSchedule().getVariantRunStrategy()))
|
||||
{
|
||||
///////////////////////////////////////////////
|
||||
// if no variants, or variant is serial mode //
|
||||
///////////////////////////////////////////////
|
||||
startProcess(process, null);
|
||||
}
|
||||
else if(QScheduleMetaData.RunStrategy.PARALLEL.equals(process.getSchedule().getVariantRunStrategy()))
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if this a "parallel", which for example means we want to have a thread for each backend variant //
|
||||
// running at the same time, get the variant records and schedule each separately //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QBackendMetaData backendMetaData = qInstance.getBackend(scheduleMetaData.getVariantBackend());
|
||||
for(QRecord qRecord : CollectionUtils.nonNullList(getBackendVariantFilteredRecords(process)))
|
||||
{
|
||||
try
|
||||
{
|
||||
startProcess(process, MapBuilder.of(backendMetaData.getVariantOptionsTableTypeValue(), qRecord.getValue(backendMetaData.getVariantOptionsTableIdField())));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("An error starting process [" + process.getLabel() + "], with backend variant data.", e, new LogPair("variantQRecord", qRecord));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.error("Unsupported Schedule Run Strategy [" + process.getSchedule().getVariantRunStrategy() + "] was provided.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(needToClearContext)
|
||||
{
|
||||
QContext.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private List<QRecord> getBackendVariantFilteredRecords(QProcessMetaData processMetaData)
|
||||
{
|
||||
List<QRecord> records = null;
|
||||
try
|
||||
{
|
||||
QScheduleMetaData scheduleMetaData = processMetaData.getSchedule();
|
||||
QBackendMetaData backendMetaData = qInstance.getBackend(scheduleMetaData.getVariantBackend());
|
||||
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(backendMetaData.getVariantOptionsTableName());
|
||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(backendMetaData.getVariantOptionsTableTypeField(), QCriteriaOperator.EQUALS, backendMetaData.getVariantOptionsTableTypeValue())));
|
||||
|
||||
QContext.init(qInstance, sessionSupplier.get());
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
records = queryOutput.getRecords();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("An error fetching variant data for process [" + processMetaData.getLabel() + "]", e);
|
||||
}
|
||||
|
||||
return (records);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void startAutomationProviderPerTable(QAutomationProviderMetaData automationProvider)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// ask the PollingAutomationPerTableRunner how many threads of itself need setup //
|
||||
// then start a scheduled executor foreach one //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
List<PollingAutomationPerTableRunner.TableActionsInterface> tableActions = PollingAutomationPerTableRunner.getTableActions(qInstance, automationProvider.getName());
|
||||
for(PollingAutomationPerTableRunner.TableActionsInterface tableAction : tableActions)
|
||||
{
|
||||
if(allowedToStart(tableAction.tableName()))
|
||||
{
|
||||
PollingAutomationPerTableRunner runner = new PollingAutomationPerTableRunner(qInstance, automationProvider.getName(), sessionSupplier, tableAction);
|
||||
StandardScheduledExecutor executor = new StandardScheduledExecutor(runner);
|
||||
|
||||
QScheduleMetaData schedule = Objects.requireNonNullElseGet(automationProvider.getSchedule(), this::getDefaultSchedule);
|
||||
|
||||
executor.setName(runner.getName());
|
||||
setScheduleInExecutor(schedule, executor);
|
||||
if(!executor.start())
|
||||
{
|
||||
LOG.warn("executor.start return false for: " + executor.getName());
|
||||
}
|
||||
|
||||
executors.add(executor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private boolean allowedToStart(String name)
|
||||
{
|
||||
String propertyName = "qqq.scheduleManager.onlyStartNamesMatching";
|
||||
String propertyValue = System.getProperty(propertyName, "");
|
||||
if(propertyValue.equals(""))
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
|
||||
return (name.matches(propertyValue));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void startQueueProvider(QQueueProviderMetaData queueProvider)
|
||||
{
|
||||
if(allowedToStart(queueProvider.getName()))
|
||||
{
|
||||
switch(queueProvider.getType())
|
||||
{
|
||||
case SQS:
|
||||
startSqsProvider((SQSQueueProviderMetaData) queueProvider);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unhandled queue provider type: " + queueProvider.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void startSqsProvider(SQSQueueProviderMetaData queueProvider)
|
||||
{
|
||||
QInstance scheduleManagerQueueInstance = qInstance;
|
||||
Supplier<QSession> scheduleManagerSessionSupplier = sessionSupplier;
|
||||
|
||||
for(QQueueMetaData queue : qInstance.getQueues().values())
|
||||
{
|
||||
if(queueProvider.getName().equals(queue.getProviderName()) && allowedToStart(queue.getName()))
|
||||
{
|
||||
SQSQueuePoller sqsQueuePoller = new SQSQueuePoller();
|
||||
sqsQueuePoller.setQueueProviderMetaData(queueProvider);
|
||||
sqsQueuePoller.setQueueMetaData(queue);
|
||||
sqsQueuePoller.setQInstance(scheduleManagerQueueInstance);
|
||||
sqsQueuePoller.setSessionSupplier(scheduleManagerSessionSupplier);
|
||||
|
||||
StandardScheduledExecutor executor = new StandardScheduledExecutor(sqsQueuePoller);
|
||||
|
||||
QScheduleMetaData schedule = Objects.requireNonNullElseGet(queue.getSchedule(),
|
||||
() -> Objects.requireNonNullElseGet(queueProvider.getSchedule(),
|
||||
this::getDefaultSchedule));
|
||||
|
||||
executor.setName(queue.getName());
|
||||
setScheduleInExecutor(schedule, executor);
|
||||
if(!executor.start())
|
||||
{
|
||||
LOG.warn("executor.start return false for: " + executor.getName());
|
||||
}
|
||||
|
||||
executors.add(executor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void startProcess(QProcessMetaData process, Map<String, Serializable> backendVariantData)
|
||||
{
|
||||
Runnable runProcess = () ->
|
||||
{
|
||||
String originalThreadName = Thread.currentThread().getName();
|
||||
|
||||
try
|
||||
{
|
||||
if(process.getSchedule().getVariantBackend() == null || QScheduleMetaData.RunStrategy.PARALLEL.equals(process.getSchedule().getVariantRunStrategy()))
|
||||
{
|
||||
QContext.init(qInstance, sessionSupplier.get());
|
||||
executeSingleProcess(process, backendVariantData);
|
||||
}
|
||||
else if(QScheduleMetaData.RunStrategy.SERIAL.equals(process.getSchedule().getVariantRunStrategy()))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if this is "serial", which for example means we want to run each backend variant one after //
|
||||
// the other in the same thread so loop over these here so that they run in same lambda function //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
for(QRecord qRecord : getBackendVariantFilteredRecords(process))
|
||||
{
|
||||
try
|
||||
{
|
||||
QContext.init(qInstance, sessionSupplier.get());
|
||||
QScheduleMetaData scheduleMetaData = process.getSchedule();
|
||||
QBackendMetaData backendMetaData = qInstance.getBackend(scheduleMetaData.getVariantBackend());
|
||||
executeSingleProcess(process, MapBuilder.of(backendMetaData.getVariantOptionsTableTypeValue(), qRecord.getValue(backendMetaData.getVariantOptionsTableIdField())));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("An error starting process [" + process.getLabel() + "], with backend variant data.", e, new LogPair("variantQRecord", qRecord));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Exception thrown running scheduled process [" + process.getName() + "]", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Thread.currentThread().setName(originalThreadName);
|
||||
QContext.clear();
|
||||
}
|
||||
};
|
||||
|
||||
StandardScheduledExecutor executor = new StandardScheduledExecutor(runProcess);
|
||||
executor.setName("process:" + process.getName());
|
||||
setScheduleInExecutor(process.getSchedule(), executor);
|
||||
if(!executor.start())
|
||||
{
|
||||
LOG.warn("executor.start return false for: " + executor.getName());
|
||||
}
|
||||
|
||||
executors.add(executor);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void executeSingleProcess(QProcessMetaData process, Map<String, Serializable> backendVariantData) throws QException
|
||||
{
|
||||
if(backendVariantData != null)
|
||||
{
|
||||
QContext.getQSession().setBackendVariants(backendVariantData);
|
||||
}
|
||||
|
||||
Thread.currentThread().setName("ScheduledProcess>" + process.getName());
|
||||
LOG.debug("Running Scheduled Process [" + process.getName() + "]");
|
||||
|
||||
RunProcessInput runProcessInput = new RunProcessInput();
|
||||
runProcessInput.setProcessName(process.getName());
|
||||
runProcessInput.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP);
|
||||
|
||||
QContext.pushAction(runProcessInput);
|
||||
|
||||
RunProcessAction runProcessAction = new RunProcessAction();
|
||||
runProcessAction.execute(runProcessInput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void setScheduleInExecutor(QScheduleMetaData schedule, StandardScheduledExecutor executor)
|
||||
{
|
||||
if(schedule.getRepeatMillis() != null)
|
||||
{
|
||||
executor.setDelayMillis(schedule.getRepeatMillis());
|
||||
}
|
||||
else
|
||||
{
|
||||
executor.setDelayMillis(1000 * schedule.getRepeatSeconds());
|
||||
}
|
||||
|
||||
if(schedule.getInitialDelayMillis() != null)
|
||||
{
|
||||
executor.setInitialDelayMillis(schedule.getInitialDelayMillis());
|
||||
}
|
||||
else if(schedule.getInitialDelaySeconds() != null)
|
||||
{
|
||||
executor.setInitialDelayMillis(1000 * schedule.getInitialDelaySeconds());
|
||||
}
|
||||
else
|
||||
{
|
||||
executor.setInitialDelayMillis(1000 * ++delayIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QScheduleMetaData getDefaultSchedule()
|
||||
{
|
||||
QScheduleMetaData schedule;
|
||||
schedule = new QScheduleMetaData()
|
||||
.withInitialDelaySeconds(delayIndex++)
|
||||
.withRepeatSeconds(60);
|
||||
return schedule;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for sessionSupplier
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setSessionSupplier(Supplier<QSession> sessionSupplier)
|
||||
{
|
||||
this.sessionSupplier = sessionSupplier;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for managedExecutors
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<StandardScheduledExecutor> getExecutors()
|
||||
{
|
||||
return executors;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void stopAsync()
|
||||
{
|
||||
for(StandardScheduledExecutor scheduledExecutor : executors)
|
||||
{
|
||||
scheduledExecutor.stopAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
static void resetSingleton()
|
||||
{
|
||||
scheduleManager = null;
|
||||
}
|
||||
|
||||
}
|
@ -1,181 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.LogPair;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
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.processes.VariantRunStrategy;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility methods used by various schedulers.
|
||||
*******************************************************************************/
|
||||
public class SchedulerUtils
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(SchedulerUtils.class);
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static boolean allowedToStart(String name)
|
||||
{
|
||||
String propertyName = "qqq.scheduleManager.onlyStartNamesMatching";
|
||||
String propertyValue = System.getProperty(propertyName, "");
|
||||
if(propertyValue.equals(""))
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
|
||||
return (name.matches(propertyValue));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void runProcess(QInstance qInstance, Supplier<QSession> sessionSupplier, QProcessMetaData process, Map<String, Serializable> backendVariantData, Map<String, Serializable> processInputValues)
|
||||
{
|
||||
String originalThreadName = Thread.currentThread().getName();
|
||||
|
||||
try
|
||||
{
|
||||
QContext.init(qInstance, sessionSupplier.get());
|
||||
|
||||
if(process.getVariantBackend() == null || VariantRunStrategy.PARALLEL.equals(process.getVariantRunStrategy()))
|
||||
{
|
||||
executeSingleProcess(process, backendVariantData, processInputValues);
|
||||
}
|
||||
else if(VariantRunStrategy.SERIAL.equals(process.getVariantRunStrategy()))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if this is "serial", which for example means we want to run each backend variant one after //
|
||||
// the other in the same thread so loop over these here so that they run in same lambda function //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
for(QRecord qRecord : getBackendVariantFilteredRecords(process))
|
||||
{
|
||||
try
|
||||
{
|
||||
QBackendMetaData backendMetaData = qInstance.getBackend(process.getVariantBackend());
|
||||
Map<String, Serializable> thisVariantData = MapBuilder.of(backendMetaData.getVariantOptionsTableTypeValue(), qRecord.getValue(backendMetaData.getVariantOptionsTableIdField()));
|
||||
executeSingleProcess(process, thisVariantData, processInputValues);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("An error starting process [" + process.getLabel() + "], with backend variant data.", e, new LogPair("variantQRecord", qRecord));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Exception thrown running scheduled process [" + process.getName() + "]", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Thread.currentThread().setName(originalThreadName);
|
||||
QContext.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void executeSingleProcess(QProcessMetaData process, Map<String, Serializable> backendVariantData, Map<String, Serializable> processInputValues) throws QException
|
||||
{
|
||||
if(backendVariantData != null)
|
||||
{
|
||||
QContext.getQSession().setBackendVariants(backendVariantData);
|
||||
}
|
||||
|
||||
Thread.currentThread().setName("ScheduledProcess>" + process.getName());
|
||||
LOG.debug("Running Scheduled Process [" + process.getName() + "] with values [" + processInputValues + "]");
|
||||
|
||||
RunProcessInput runProcessInput = new RunProcessInput();
|
||||
runProcessInput.setProcessName(process.getName());
|
||||
|
||||
for(Map.Entry<String, Serializable> entry : CollectionUtils.nonNullMap(processInputValues).entrySet())
|
||||
{
|
||||
runProcessInput.withValue(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
runProcessInput.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP);
|
||||
|
||||
QContext.pushAction(runProcessInput);
|
||||
|
||||
RunProcessAction runProcessAction = new RunProcessAction();
|
||||
runProcessAction.execute(runProcessInput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static List<QRecord> getBackendVariantFilteredRecords(QProcessMetaData processMetaData)
|
||||
{
|
||||
List<QRecord> records = null;
|
||||
try
|
||||
{
|
||||
QBackendMetaData backendMetaData = QContext.getQInstance().getBackend(processMetaData.getVariantBackend());
|
||||
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(backendMetaData.getVariantOptionsTableName());
|
||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(backendMetaData.getVariantOptionsTableTypeField(), QCriteriaOperator.EQUALS, backendMetaData.getVariantOptionsTableTypeValue())));
|
||||
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
records = queryOutput.getRecords();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("An error fetching variant data for process [" + processMetaData.getLabel() + "]", e);
|
||||
}
|
||||
|
||||
return (records);
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
@ -19,7 +19,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler.simple;
|
||||
package com.kingsrook.qqq.backend.core.scheduler;
|
||||
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
@ -32,10 +32,8 @@ import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Standard class ran by SimpleScheduler. Takes a Runnable in its constructor -
|
||||
** that's the code that actually executes. Internally, this class will launch
|
||||
** a newSingleThreadScheduledExecutor / ScheduledExecutorService to run the
|
||||
** runnable on a repeating delay.
|
||||
** Standard class ran by ScheduleManager. Takes a Runnable in its constructor -
|
||||
** that's the code that actually executes.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class StandardScheduledExecutor
|
@ -1,90 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler.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.QScheduleManager;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Management process to reschedule all scheduled jobs (in all schedulers).
|
||||
*******************************************************************************/
|
||||
public class RescheduleAllJobsProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QProcessMetaData produce(QInstance qInstance) throws QException
|
||||
{
|
||||
return new QProcessMetaData()
|
||||
.withName(getClass().getSimpleName())
|
||||
.withLabel("Reschedule all Scheduled Jobs")
|
||||
.withIcon(new QIcon("update"))
|
||||
.withStepList(List.of(
|
||||
new QFrontendStepMetaData()
|
||||
.withName("confirm")
|
||||
.withComponent(new NoCodeWidgetFrontendComponentMetaData()
|
||||
.withOutput(new WidgetHtmlLine().withVelocityTemplate("Please confirm you wish to reschedule all jobs."))),
|
||||
new QBackendStepMetaData()
|
||||
.withName("execute")
|
||||
.withCode(new QCodeReference(getClass())),
|
||||
new QFrontendStepMetaData()
|
||||
.withName("results")
|
||||
.withComponent(new NoCodeWidgetFrontendComponentMetaData()
|
||||
.withOutput(new WidgetHtmlLine().withVelocityTemplate("All jobs have been rescheduled.")))));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
QScheduleManager.getInstance().setupAllSchedules();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Error setting up all scheduled jobs.", e));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler.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.QScheduleManager;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Management process to unschedule all scheduled jobs (in all schedulers).
|
||||
*******************************************************************************/
|
||||
public class UnscheduleAllJobsProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QProcessMetaData produce(QInstance qInstance) throws QException
|
||||
{
|
||||
return new QProcessMetaData()
|
||||
.withName(getClass().getSimpleName())
|
||||
.withLabel("Unschedule all Scheduled Jobs")
|
||||
.withIcon(new QIcon("update_disabled"))
|
||||
.withStepList(List.of(
|
||||
new QFrontendStepMetaData()
|
||||
.withName("confirm")
|
||||
.withComponent(new NoCodeWidgetFrontendComponentMetaData()
|
||||
.withOutput(new WidgetHtmlLine().withVelocityTemplate("Please confirm you wish to unschedule all jobs."))),
|
||||
new QBackendStepMetaData()
|
||||
.withName("execute")
|
||||
.withCode(new QCodeReference(getClass())),
|
||||
new QFrontendStepMetaData()
|
||||
.withName("results")
|
||||
.withComponent(new NoCodeWidgetFrontendComponentMetaData()
|
||||
.withOutput(new WidgetHtmlLine().withVelocityTemplate("All jobs have been unscheduled.")))));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
QScheduleManager.getInstance().unscheduleAll();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Error unscheduling all scheduled jobs.", e));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler.quartz;
|
||||
|
||||
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.Trigger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public record QuartzJobAndTriggerWrapper(JobDetail jobDetail, Trigger trigger, Trigger.TriggerState triggerState)
|
||||
{
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler.quartz;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.context.CapturedContext;
|
||||
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.schedulable.SchedulableType;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.runner.SchedulableRunner;
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QuartzJobRunner implements Job
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QuartzJobRunner.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void execute(JobExecutionContext context) throws JobExecutionException
|
||||
{
|
||||
CapturedContext capturedContext = QContext.capture();
|
||||
try
|
||||
{
|
||||
QuartzScheduler quartzScheduler = QuartzScheduler.getInstance();
|
||||
QInstance qInstance = quartzScheduler.getQInstance();
|
||||
QContext.init(qInstance, quartzScheduler.getSessionSupplier().get());
|
||||
|
||||
SchedulableType schedulableType = qInstance.getSchedulableType(context.getJobDetail().getJobDataMap().getString("type"));
|
||||
Map<String, Object> params = (Map<String, Object>) context.getJobDetail().getJobDataMap().get("params");
|
||||
|
||||
SchedulableRunner schedulableRunner = QCodeLoader.getAdHoc(SchedulableRunner.class, schedulableType.getRunner());
|
||||
schedulableRunner.run(params);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error running QuartzJob", e, logPair("jobContext", context));
|
||||
}
|
||||
finally
|
||||
{
|
||||
QContext.init(capturedContext);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,742 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler.quartz;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
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.scheduleing.QScheduleMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.QSchedulerInterface;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.SchedulableType;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.identity.SchedulableIdentity;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.memoization.AnyKey;
|
||||
import com.kingsrook.qqq.backend.core.utils.memoization.Memoization;
|
||||
import org.quartz.CronExpression;
|
||||
import org.quartz.CronScheduleBuilder;
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobBuilder;
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.JobKey;
|
||||
import org.quartz.ScheduleBuilder;
|
||||
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;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton to provide access between QQQ and the quartz Scheduler system.
|
||||
*******************************************************************************/
|
||||
public class QuartzScheduler implements QSchedulerInterface
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QuartzScheduler.class);
|
||||
|
||||
private static QuartzScheduler quartzScheduler = null;
|
||||
|
||||
private final QInstance qInstance;
|
||||
private String schedulerName;
|
||||
private Supplier<QSession> sessionSupplier;
|
||||
|
||||
private Scheduler scheduler;
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// create memoization objects for some quartz-query functions, that we'll only want to //
|
||||
// use during our setup routine, when we'd query it many times over and over again. //
|
||||
// So default to a timeout of 0 (effectively disabling memoization). then in the //
|
||||
// start-of-setup and end-of-setup methods, temporarily increase, then re-decrease //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
private Memoization<AnyKey, List<String>> jobGroupNamesMemoization = new Memoization<AnyKey, List<String>>()
|
||||
.withTimeout(Duration.of(0, ChronoUnit.SECONDS));
|
||||
|
||||
private Memoization<String, Set<JobKey>> jobKeyNamesMemoization = new Memoization<String, Set<JobKey>>()
|
||||
.withTimeout(Duration.of(0, ChronoUnit.SECONDS));
|
||||
|
||||
private Memoization<AnyKey, List<QuartzJobAndTriggerWrapper>> queryQuartzMemoization = new Memoization<AnyKey, List<QuartzJobAndTriggerWrapper>>()
|
||||
.withTimeout(Duration.of(0, ChronoUnit.SECONDS));
|
||||
|
||||
private List<Memoization<?, ?>> allMemoizations = List.of(jobGroupNamesMemoization, jobKeyNamesMemoization, queryQuartzMemoization);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// vars used during the setup routine, to figure out what jobs need deleted. //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
private boolean insideSetup = false;
|
||||
private List<QuartzJobAndTriggerWrapper> scheduledJobsAtStartOfSetup = new ArrayList<>();
|
||||
private List<QuartzJobAndTriggerWrapper> scheduledJobsAtEndOfSetup = new ArrayList<>();
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// track if the instance is past the server's startup routine. //
|
||||
// for quartz - we'll use this to know if we're allowed to schedule jobs. //
|
||||
// that is - during server startup, we don't want to the schedule & unschedule //
|
||||
// routine, which could potentially have serve concurrency problems //
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
private boolean pastStartup = false;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QuartzScheduler(QInstance qInstance, String schedulerName, Supplier<QSession> sessionSupplier)
|
||||
{
|
||||
this.qInstance = qInstance;
|
||||
this.schedulerName = schedulerName;
|
||||
this.sessionSupplier = sessionSupplier;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton initiator - e.g., must be called to initially initialize the singleton
|
||||
** before anyone else calls getInstance (they'll get an error if they call that first).
|
||||
*******************************************************************************/
|
||||
public static QuartzScheduler initInstance(QInstance qInstance, String schedulerName, Properties quartzProperties, Supplier<QSession> sessionSupplier) throws SchedulerException
|
||||
{
|
||||
if(quartzScheduler == null)
|
||||
{
|
||||
quartzScheduler = new QuartzScheduler(qInstance, schedulerName, sessionSupplier);
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// Grab the Scheduler instance from the Factory //
|
||||
// initialize it with the properties we took in as input //
|
||||
///////////////////////////////////////////////////////////
|
||||
StdSchedulerFactory schedulerFactory = new StdSchedulerFactory();
|
||||
schedulerFactory.initialize(quartzProperties);
|
||||
quartzScheduler.scheduler = schedulerFactory.getScheduler();
|
||||
}
|
||||
return (quartzScheduler);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton accessor
|
||||
*******************************************************************************/
|
||||
public static QuartzScheduler getInstance()
|
||||
{
|
||||
if(quartzScheduler == null)
|
||||
{
|
||||
throw (new IllegalStateException("QuartzScheduler singleton has not been init'ed (call initInstance)."));
|
||||
}
|
||||
return (quartzScheduler);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getSchedulerName()
|
||||
{
|
||||
return (schedulerName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void start()
|
||||
{
|
||||
this.pastStartup = true;
|
||||
|
||||
try
|
||||
{
|
||||
//////////////////////
|
||||
// and start it off //
|
||||
//////////////////////
|
||||
scheduler.start();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error starting quartz scheduler", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void doNotStart()
|
||||
{
|
||||
this.pastStartup = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
try
|
||||
{
|
||||
scheduler.shutdown(true);
|
||||
}
|
||||
catch(SchedulerException e)
|
||||
{
|
||||
LOG.error("Error shutting down (stopping) quartz scheduler", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void stopAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
scheduler.shutdown(false);
|
||||
}
|
||||
catch(SchedulerException e)
|
||||
{
|
||||
LOG.error("Error shutting down (stopping) quartz scheduler", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void setupSchedulable(SchedulableIdentity schedulableIdentity, SchedulableType schedulableType, Map<String, Serializable> parameters, QScheduleMetaData schedule, boolean allowedToStart)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// only actually schedule things if we're past the server startup routine //
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
if(!pastStartup)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> jobData = new HashMap<>();
|
||||
jobData.put("params", parameters);
|
||||
jobData.put("type", schedulableType.getName());
|
||||
|
||||
scheduleJob(schedulableIdentity, schedulableType.getName(), QuartzJobRunner.class, jobData, schedule, allowedToStart);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void startOfSetupSchedules()
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// only actually schedule things if we're past the server startup routine //
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
if(!pastStartup)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.insideSetup = true;
|
||||
this.allMemoizations.forEach(m -> m.setTimeout(Duration.ofSeconds(5)));
|
||||
|
||||
try
|
||||
{
|
||||
this.scheduledJobsAtStartOfSetup = queryQuartz();
|
||||
this.scheduledJobsAtEndOfSetup = new ArrayList<>();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error querying quartz for the currently scheduled jobs during startup - will not be able to delete no-longer-needed jobs!", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void endOfSetupSchedules()
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// only actually schedule things if we're past the server startup routine //
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
if(!pastStartup)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.insideSetup = false;
|
||||
this.allMemoizations.forEach(m -> m.setTimeout(Duration.ofSeconds(0)));
|
||||
|
||||
if(this.scheduledJobsAtStartOfSetup == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Set<JobKey> startJobKeys = this.scheduledJobsAtStartOfSetup.stream().map(w -> w.jobDetail().getKey()).collect(Collectors.toSet());
|
||||
Set<JobKey> endJobKeys = this.scheduledJobsAtEndOfSetup.stream().map(w -> w.jobDetail().getKey()).collect(Collectors.toSet());
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// remove all 'end' keys from the set of start keys. any left-over start-keys need to be deleted. //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
startJobKeys.removeAll(endJobKeys);
|
||||
for(JobKey jobKey : startJobKeys)
|
||||
{
|
||||
LOG.info("Deleting job that had previously been scheduled, but doesn't appear to be any more", logPair("jobKey", jobKey));
|
||||
deleteJob(jobKey);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error trying to clean up no-longer-needed jobs at end of scheduler setup", e);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// reset these lists, no need to keep them around //
|
||||
////////////////////////////////////////////////////
|
||||
this.scheduledJobsAtStartOfSetup = null;
|
||||
this.scheduledJobsAtEndOfSetup = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private boolean scheduleJob(SchedulableIdentity schedulableIdentity, String groupName, Class<? extends Job> jobClass, Map<String, Object> jobData, QScheduleMetaData scheduleMetaData, boolean allowedToStart)
|
||||
{
|
||||
try
|
||||
{
|
||||
/////////////////////////
|
||||
// Define job instance //
|
||||
/////////////////////////
|
||||
JobKey jobKey = new JobKey(schedulableIdentity.getIdentity(), groupName);
|
||||
JobDetail jobDetail = JobBuilder.newJob(jobClass)
|
||||
.withIdentity(jobKey)
|
||||
.withDescription(schedulableIdentity.getDescription())
|
||||
.storeDurably()
|
||||
.requestRecovery() // todo - our frequent repeaters, maybe nice to say false here
|
||||
.build();
|
||||
|
||||
jobDetail.getJobDataMap().putAll(jobData);
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// map the qqq schedule meta data to a quartz schedule //
|
||||
/////////////////////////////////////////////////////////
|
||||
ScheduleBuilder<?> scheduleBuilder;
|
||||
if(scheduleMetaData.isCron())
|
||||
{
|
||||
CronExpression cronExpression = new CronExpression(scheduleMetaData.getCronExpression());
|
||||
cronExpression.setTimeZone(TimeZone.getTimeZone(scheduleMetaData.getCronTimeZoneId()));
|
||||
scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
|
||||
}
|
||||
else
|
||||
{
|
||||
long intervalMillis = Objects.requireNonNullElseGet(scheduleMetaData.getRepeatMillis(), () -> scheduleMetaData.getRepeatSeconds() * 1000);
|
||||
scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
|
||||
.withIntervalInMilliseconds(intervalMillis)
|
||||
.repeatForever();
|
||||
}
|
||||
|
||||
Date startAt = new Date();
|
||||
if(scheduleMetaData.getInitialDelayMillis() != null)
|
||||
{
|
||||
startAt.setTime(startAt.getTime() + scheduleMetaData.getInitialDelayMillis());
|
||||
}
|
||||
else if(scheduleMetaData.getInitialDelaySeconds() != null)
|
||||
{
|
||||
startAt.setTime(startAt.getTime() + scheduleMetaData.getInitialDelaySeconds() * 1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// by default, put a 3-second delay on everything we schedule //
|
||||
// this gives us a chance to re-pause if the job was previously paused, but then we re-schedule it. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
startAt.setTime(startAt.getTime() + 3000);
|
||||
}
|
||||
|
||||
///////////////////////////////////////
|
||||
// Define a Trigger for the schedule //
|
||||
///////////////////////////////////////
|
||||
Trigger trigger = TriggerBuilder.newTrigger()
|
||||
.withIdentity(new TriggerKey(schedulableIdentity.getIdentity(), groupName))
|
||||
.withDescription(schedulableIdentity.getDescription() + " - " + getScheduleDescriptionForTrigger(scheduleMetaData))
|
||||
.forJob(jobKey)
|
||||
.withSchedule(scheduleBuilder)
|
||||
// .startAt(startAt)
|
||||
.build();
|
||||
|
||||
///////////////////////////////////////
|
||||
// Schedule the job with the trigger //
|
||||
///////////////////////////////////////
|
||||
addOrReplaceJobAndTrigger(jobKey, jobDetail, trigger);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// if we're inside the setup event (e.g., initial startup), then capture //
|
||||
// this job as one that is currently active and should be kept. //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
if(insideSetup)
|
||||
{
|
||||
scheduledJobsAtEndOfSetup.add(new QuartzJobAndTriggerWrapper(jobDetail, trigger, null));
|
||||
}
|
||||
|
||||
return (true);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error scheduling job", e, logPair("name", schedulableIdentity.getIdentity()), logPair("group", groupName));
|
||||
return (false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String getScheduleDescriptionForTrigger(QScheduleMetaData scheduleMetaData)
|
||||
{
|
||||
if(StringUtils.hasContent(scheduleMetaData.getDescription()))
|
||||
{
|
||||
return scheduleMetaData.getDescription();
|
||||
}
|
||||
|
||||
if(StringUtils.hasContent(scheduleMetaData.getCronExpression()))
|
||||
{
|
||||
return "cron expression: " + scheduleMetaData.getCronExpression() + (StringUtils.hasContent(scheduleMetaData.getCronTimeZoneId()) ? " time zone: " + scheduleMetaData.getCronTimeZoneId() : "");
|
||||
}
|
||||
|
||||
if(scheduleMetaData.getRepeatSeconds() != null)
|
||||
{
|
||||
return "repeat seconds: " + scheduleMetaData.getRepeatSeconds();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void unscheduleSchedulable(SchedulableIdentity schedulableIdentity, SchedulableType schedulableType)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// only actually schedule things if we're past the server startup routine //
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
if(!pastStartup)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
deleteJob(new JobKey(schedulableIdentity.getIdentity(), schedulableType.getName()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void unscheduleAll() throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
for(QuartzJobAndTriggerWrapper wrapper : queryQuartz())
|
||||
{
|
||||
deleteJob(new JobKey(wrapper.jobDetail().getKey().getName(), wrapper.jobDetail().getKey().getGroup()));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Error unscheduling all quartz jobs", e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void addOrReplaceJobAndTrigger(JobKey jobKey, JobDetail jobDetail, Trigger trigger) throws SchedulerException
|
||||
{
|
||||
boolean isJobAlreadyScheduled = isJobAlreadyScheduled(jobKey);
|
||||
if(isJobAlreadyScheduled)
|
||||
{
|
||||
boolean wasPaused = wasExistingJobPaused(jobKey);
|
||||
|
||||
this.scheduler.scheduleJob(jobDetail, Set.of(trigger), true); // note, true flag here replaces if already present.
|
||||
LOG.info("Re-scheduled job", logPair("jobKey", jobKey));
|
||||
|
||||
if(wasPaused)
|
||||
{
|
||||
LOG.info("Re-pausing job", logPair("jobKey", jobKey));
|
||||
pauseJob(jobKey.getName(), jobKey.getGroup());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.scheduler.scheduleJob(jobDetail, trigger);
|
||||
LOG.info("Scheduled new job", logPair("jobKey", jobKey));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private boolean wasExistingJobPaused(JobKey jobKey) throws SchedulerException
|
||||
{
|
||||
List<QuartzJobAndTriggerWrapper> quartzJobAndTriggerWrappers = queryQuartz();
|
||||
Optional<QuartzJobAndTriggerWrapper> existingWrapper = quartzJobAndTriggerWrappers.stream().filter(w -> w.jobDetail().getKey().equals(jobKey)).findFirst();
|
||||
if(existingWrapper.isPresent())
|
||||
{
|
||||
if(Trigger.TriggerState.PAUSED.equals(existingWrapper.get().triggerState()))
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
}
|
||||
|
||||
return (false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private boolean isJobAlreadyScheduled(JobKey jobKey) throws SchedulerException
|
||||
{
|
||||
Optional<List<String>> jobGroupNames = jobGroupNamesMemoization.getResult(AnyKey.getInstance(), (x) -> scheduler.getJobGroupNames());
|
||||
if(jobGroupNames.isEmpty())
|
||||
{
|
||||
throw (new SchedulerException("Error getting job group names"));
|
||||
}
|
||||
|
||||
for(String group : jobGroupNames.get())
|
||||
{
|
||||
Optional<Set<JobKey>> jobKeys = jobKeyNamesMemoization.getResult(group, (x) -> scheduler.getJobKeys(GroupMatcher.groupEquals(group)));
|
||||
if(jobKeys.isEmpty())
|
||||
{
|
||||
throw (new SchedulerException("Error getting job keys"));
|
||||
}
|
||||
|
||||
if(jobKeys.get().contains(jobKey))
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
}
|
||||
|
||||
return (false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public boolean deleteJob(JobKey jobKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// https://www.quartz-scheduler.org/documentation/quartz-2.3.0/cookbook/UnscheduleJob.html //
|
||||
// Deleting a Job and Unscheduling All of Its Triggers //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(isJobAlreadyScheduled(jobKey))
|
||||
{
|
||||
boolean result = scheduler.deleteJob(jobKey);
|
||||
LOG.info("Attempted to delete quartz job", logPair("jobKey", jobKey), logPair("deleteJobResult", result));
|
||||
return (result);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////
|
||||
// return true to indicate, we're good //
|
||||
/////////////////////////////////////////
|
||||
LOG.info("Request to delete quartz job, but it is not already scheduled.", logPair("jobKey", jobKey));
|
||||
return (true);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error deleting job", e, logPair("jobKey", jobKey));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for qInstance
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QInstance getQInstance()
|
||||
{
|
||||
return qInstance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for sessionSupplier
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Supplier<QSession> getSessionSupplier()
|
||||
{
|
||||
return sessionSupplier;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void pauseAll() throws SchedulerException
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// lesson from past self to future self: //
|
||||
// pauseAll creates paused-group entries for all jobs - //
|
||||
// and so they can only really be resumed by a resumeAll call... //
|
||||
// even newly scheduled things become paused. Which can be quite confusing. //
|
||||
// so, we don't want pause all. //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// this.scheduler.pauseAll();
|
||||
|
||||
List<QuartzJobAndTriggerWrapper> quartzJobAndTriggerWrappers = queryQuartz();
|
||||
for(QuartzJobAndTriggerWrapper wrapper : quartzJobAndTriggerWrappers)
|
||||
{
|
||||
this.pauseJob(wrapper.jobDetail().getKey().getName(), wrapper.jobDetail().getKey().getGroup());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void resumeAll() throws SchedulerException
|
||||
{
|
||||
//////////////////////////////////////////////////
|
||||
// this seems okay, even though pauseAll isn't. //
|
||||
//////////////////////////////////////////////////
|
||||
this.scheduler.resumeAll();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void pauseJob(String jobName, String groupName) throws SchedulerException
|
||||
{
|
||||
LOG.info("Request to pause job", logPair("jobName", jobName));
|
||||
this.scheduler.pauseJob(new JobKey(jobName, groupName));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void resumeJob(String jobName, String groupName) throws SchedulerException
|
||||
{
|
||||
LOG.info("Request to resume job", logPair("jobName", jobName));
|
||||
this.scheduler.resumeJob(new JobKey(jobName, groupName));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
List<QuartzJobAndTriggerWrapper> queryQuartz() throws SchedulerException
|
||||
{
|
||||
return queryQuartzMemoization.getResultThrowing(AnyKey.getInstance(), (x) ->
|
||||
{
|
||||
List<QuartzJobAndTriggerWrapper> rs = new ArrayList<>();
|
||||
|
||||
for(String group : scheduler.getJobGroupNames())
|
||||
{
|
||||
Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.groupEquals(group));
|
||||
for(JobKey jobKey : jobKeys)
|
||||
{
|
||||
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
|
||||
List<? extends Trigger> triggersOfJob = scheduler.getTriggersOfJob(jobKey);
|
||||
for(Trigger trigger : triggersOfJob)
|
||||
{
|
||||
Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
|
||||
rs.add(new QuartzJobAndTriggerWrapper(jobDetail, trigger, triggerState));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (rs);
|
||||
}).orElse(null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void unInit()
|
||||
{
|
||||
///////////////////////////////////////////////////
|
||||
// resetting the singleton should be sufficient! //
|
||||
///////////////////////////////////////////////////
|
||||
quartzScheduler = null;
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Manage process to pause all quartz jobs
|
||||
*******************************************************************************/
|
||||
public class PauseAllQuartzJobsProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QProcessMetaData produce(QInstance qInstance) throws QException
|
||||
{
|
||||
return new QProcessMetaData()
|
||||
.withName(getClass().getSimpleName())
|
||||
.withLabel("Pause All Quartz Jobs")
|
||||
.withStepList(List.of(
|
||||
new QFrontendStepMetaData()
|
||||
.withName("confirm")
|
||||
.withComponent(new NoCodeWidgetFrontendComponentMetaData()
|
||||
.withOutput(new WidgetHtmlLine().withVelocityTemplate("Please confirm you wish to pause all quartz jobs."))),
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler.quartz.processes;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
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.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
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<QProcessMetaData>
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QProcessMetaData produce(QInstance qInstance) throws QException
|
||||
{
|
||||
String tableName = "quartzTriggers";
|
||||
|
||||
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"))
|
||||
.withReviewStepRecordFields(List.of(
|
||||
new QFieldMetaData("id", QFieldType.LONG),
|
||||
new QFieldMetaData("jobName", QFieldType.STRING),
|
||||
new QFieldMetaData("jobGroup", QFieldType.STRING)))
|
||||
.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("jobName"), record.getValueString("jobGroup"));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Error pausing jobs", e));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Manage process to resume all quartz jobs
|
||||
*******************************************************************************/
|
||||
public class ResumeAllQuartzJobsProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QProcessMetaData produce(QInstance qInstance) throws QException
|
||||
{
|
||||
return new QProcessMetaData()
|
||||
.withName(getClass().getSimpleName())
|
||||
.withLabel("Resume All Quartz Jobs")
|
||||
.withStepList(List.of(
|
||||
new QFrontendStepMetaData()
|
||||
.withName("confirm")
|
||||
.withComponent(new NoCodeWidgetFrontendComponentMetaData()
|
||||
.withOutput(new WidgetHtmlLine().withVelocityTemplate("Please confirm you wish to resume all quartz jobs."))),
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler.quartz.processes;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
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.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
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<QProcessMetaData>
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QProcessMetaData produce(QInstance qInstance) throws QException
|
||||
{
|
||||
String tableName = "quartzTriggers";
|
||||
|
||||
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"))
|
||||
.withReviewStepRecordFields(List.of(
|
||||
new QFieldMetaData("id", QFieldType.LONG),
|
||||
new QFieldMetaData("jobName", QFieldType.STRING),
|
||||
new QFieldMetaData("jobGroup", QFieldType.STRING)))
|
||||
.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("jobName"), record.getValueString("jobGroup"));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Error resuming jobs", e));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler.quartz.tables;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostQueryCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import org.apache.commons.lang3.SerializationUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QuartzJobDataPostQueryCustomizer extends AbstractPostQueryCustomizer
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QuartzJobDataPostQueryCustomizer.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> apply(List<QRecord> records)
|
||||
{
|
||||
for(QRecord record : records)
|
||||
{
|
||||
if(record.getValue("jobData") != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// this field has a blob of essentially a serialized map - so, deserialize that, then convert to JSON //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
byte[] value = record.getValueByteArray("jobData");
|
||||
if(value.length > 0)
|
||||
{
|
||||
Object deserialize = SerializationUtils.deserialize(value);
|
||||
String json = JsonUtils.toJson(deserialize);
|
||||
record.setValue("jobData", json);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.info("Error deserializing quartz job data", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (records);
|
||||
}
|
||||
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler.schedulable;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class SchedulableType
|
||||
{
|
||||
private String name;
|
||||
private QCodeReference runner;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for name
|
||||
*******************************************************************************/
|
||||
public String getName()
|
||||
{
|
||||
return (this.name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for name
|
||||
*******************************************************************************/
|
||||
public void setName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for name
|
||||
*******************************************************************************/
|
||||
public SchedulableType withName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for runner
|
||||
*******************************************************************************/
|
||||
public QCodeReference getRunner()
|
||||
{
|
||||
return (this.runner);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for runner
|
||||
*******************************************************************************/
|
||||
public void setRunner(QCodeReference runner)
|
||||
{
|
||||
this.runner = runner;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for runner
|
||||
*******************************************************************************/
|
||||
public SchedulableType withRunner(QCodeReference runner)
|
||||
{
|
||||
this.runner = runner;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -1,121 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler.schedulable.identity;
|
||||
|
||||
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Basic implementation of interface for identifying schedulable things
|
||||
*******************************************************************************/
|
||||
public class BasicSchedulableIdentity implements SchedulableIdentity
|
||||
{
|
||||
private String identity;
|
||||
private String description;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public BasicSchedulableIdentity(String identity, String description)
|
||||
{
|
||||
if(!StringUtils.hasContent(identity))
|
||||
{
|
||||
throw (new IllegalArgumentException("Identity may not be null or empty."));
|
||||
}
|
||||
|
||||
this.identity = identity;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if(this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if(o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
BasicSchedulableIdentity that = (BasicSchedulableIdentity) o;
|
||||
return Objects.equals(identity, that.identity);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(identity);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getIdentity()
|
||||
{
|
||||
return identity;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for description
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getDescription()
|
||||
{
|
||||
return description;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return getIdentity();
|
||||
}
|
||||
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler.schedulable.identity;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unique identifier for a thing that can be scheduled
|
||||
*******************************************************************************/
|
||||
public interface SchedulableIdentity
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
boolean equals(Object that);
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
int hashCode();
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
String getIdentity();
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** should NOT be part of equals & has code
|
||||
*******************************************************************************/
|
||||
String getDescription();
|
||||
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler.schedulable.identity;
|
||||
|
||||
|
||||
import java.util.HashMap;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.polling.PollingAutomationPerTableRunner;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJob;
|
||||
import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJobType;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.SchedulableType;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.runner.SchedulableRunner;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Factory to produce SchedulableIdentity objects
|
||||
*******************************************************************************/
|
||||
public class SchedulableIdentityFactory
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
** Factory to create one of these for a scheduled job record
|
||||
*******************************************************************************/
|
||||
public static BasicSchedulableIdentity of(ScheduledJob scheduledJob)
|
||||
{
|
||||
String description = "";
|
||||
ScheduledJobType scheduledJobType = ScheduledJobType.getById(scheduledJob.getType());
|
||||
if(scheduledJobType != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
SchedulableType schedulableType = QContext.getQInstance().getSchedulableType(scheduledJob.getType());
|
||||
SchedulableRunner runner = QCodeLoader.getAdHoc(SchedulableRunner.class, schedulableType.getRunner());
|
||||
description = runner.getDescription(new HashMap<>(scheduledJob.getJobParametersMap()));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
description = "type: " + scheduledJobType;
|
||||
}
|
||||
}
|
||||
|
||||
return new BasicSchedulableIdentity("scheduledJob:" + scheduledJob.getId(), description);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static BasicSchedulableIdentity of(QProcessMetaData process)
|
||||
{
|
||||
return new BasicSchedulableIdentity("process:" + process.getName(), "Process: " + process.getName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static SchedulableIdentity of(QQueueMetaData queue)
|
||||
{
|
||||
return new BasicSchedulableIdentity("queue:" + queue.getName(), "Queue: " + queue.getName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static SchedulableIdentity of(PollingAutomationPerTableRunner.TableActionsInterface tableActions)
|
||||
{
|
||||
return new BasicSchedulableIdentity("tableAutomations:" + tableActions.tableName() + "." + tableActions.status(), "TableAutomations: " + tableActions.tableName() + "." + tableActions.status());
|
||||
}
|
||||
}
|
@ -1,188 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler.schedulable.runner;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
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.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.SchedulerUtils;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.identity.SchedulableIdentity;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Schedulable process runner - e.g., how a QProcess is run by a scheduler.
|
||||
*******************************************************************************/
|
||||
public class SchedulableProcessRunner implements SchedulableRunner
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(SchedulableProcessRunner.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void run(Map<String, Object> params)
|
||||
{
|
||||
String processName = ValueUtils.getValueAsString(params.get("processName"));
|
||||
|
||||
///////////////////////////////////////
|
||||
// get the process from the instance //
|
||||
///////////////////////////////////////
|
||||
QInstance qInstance = QContext.getQInstance();
|
||||
QProcessMetaData process = qInstance.getProcess(processName);
|
||||
if(process == null)
|
||||
{
|
||||
LOG.warn("Could not find scheduled process in QInstance", logPair("processName", processName));
|
||||
return;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// if the job has variant data, get it ready //
|
||||
///////////////////////////////////////////////
|
||||
Map<String, Serializable> backendVariantData = null;
|
||||
if(params.containsKey("backendVariantData"))
|
||||
{
|
||||
backendVariantData = (Map<String, Serializable>) params.get("backendVariantData");
|
||||
}
|
||||
|
||||
Map<String, Serializable> processInputValues = buildProcessInputValuesMap(params, process);
|
||||
|
||||
/////////////
|
||||
// run it. //
|
||||
/////////////
|
||||
LOG.debug("Running scheduled process", logPair("processName", processName));
|
||||
SchedulerUtils.runProcess(qInstance, () -> QContext.getQSession(), qInstance.getProcess(processName), backendVariantData, processInputValues);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void validateParams(SchedulableIdentity schedulableIdentity, Map<String, Object> paramMap) throws QException
|
||||
{
|
||||
String processName = ValueUtils.getValueAsString(paramMap.get("processName"));
|
||||
if(!StringUtils.hasContent(processName))
|
||||
{
|
||||
throw (new QException("Missing scheduledJobParameter with key [processName] in " + schedulableIdentity));
|
||||
}
|
||||
|
||||
QProcessMetaData process = QContext.getQInstance().getProcess(processName);
|
||||
if(process == null)
|
||||
{
|
||||
throw (new QException("Unrecognized processName [" + processName + "] in " + schedulableIdentity));
|
||||
}
|
||||
|
||||
if(process.getSchedule() != null)
|
||||
{
|
||||
throw (new QException("Process [" + processName + "] has a schedule in its metaData - so it should not be dynamically scheduled via a scheduled job! " + schedulableIdentity));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getDescription(Map<String, Object> params)
|
||||
{
|
||||
return "Process: " + params.get("processName");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static Map<String, Serializable> buildProcessInputValuesMap(Map<String, Object> params, QProcessMetaData process)
|
||||
{
|
||||
Map<String, Serializable> processInputValues = new HashMap<>();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// track which keys need processed - start by removing ones we know we handle above //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
Set<String> keys = new HashSet<>(params.keySet());
|
||||
keys.remove("processName");
|
||||
keys.remove("backendVariantData");
|
||||
|
||||
if(!keys.isEmpty())
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// first make a pass going over the process's identified input fields - //
|
||||
// getting values from the quartz job data map, and putting them into //
|
||||
// the process input value map as the field's type (if we can) //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
for(QFieldMetaData inputField : process.getInputFields())
|
||||
{
|
||||
String fieldName = inputField.getName();
|
||||
if(params.containsKey(fieldName))
|
||||
{
|
||||
Object value = params.get(fieldName);
|
||||
try
|
||||
{
|
||||
processInputValues.put(fieldName, ValueUtils.getValueAsFieldType(inputField.getType(), value));
|
||||
keys.remove(fieldName);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error getting process input value from quartz job data map", e, logPair("fieldName", fieldName), logPair("value", value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if any values are left in the map (based on keys set that we're removing from) //
|
||||
// then try to put those in the input map (assuming they can be cast to Serializable) //
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
for(String key : keys)
|
||||
{
|
||||
Object value = params.get(key);
|
||||
try
|
||||
{
|
||||
processInputValues.put(key, (Serializable) value);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error getting process input value from quartz job data map", e, logPair("key", key), logPair("value", value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return processInputValues;
|
||||
}
|
||||
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler.schedulable.runner;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.identity.SchedulableIdentity;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Interface for different types of schedulabe things that can be run
|
||||
*******************************************************************************/
|
||||
public interface SchedulableRunner
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void run(Map<String, Object> params);
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void validateParams(SchedulableIdentity schedulableIdentity, Map<String, Object> paramMap) throws QException;
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
String getDescription(Map<String, Object> params);
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler.schedulable.runner;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.queues.SQSQueuePoller;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
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.queues.QQueueMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueProviderMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzScheduler;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.identity.SchedulableIdentity;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Schedulable SQSQueue runner - e.g., how an SQSQueuePoller is run by a scheduler.
|
||||
*******************************************************************************/
|
||||
public class SchedulableSQSQueueRunner implements SchedulableRunner
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(SchedulableSQSQueueRunner.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void run(Map<String, Object> params)
|
||||
{
|
||||
QInstance qInstance = QuartzScheduler.getInstance().getQInstance();
|
||||
|
||||
String queueName = ValueUtils.getValueAsString(params.get("queueName"));
|
||||
if(!StringUtils.hasContent(queueName))
|
||||
{
|
||||
LOG.warn("Missing queueName in params.");
|
||||
return;
|
||||
}
|
||||
|
||||
QQueueMetaData queue = qInstance.getQueue(queueName);
|
||||
if(queue == null)
|
||||
{
|
||||
LOG.warn("Unrecognized queueName [" + queueName + "]");
|
||||
return;
|
||||
}
|
||||
|
||||
QQueueProviderMetaData queueProvider = qInstance.getQueueProvider(queue.getProviderName());
|
||||
if(!(queueProvider instanceof SQSQueueProviderMetaData))
|
||||
{
|
||||
LOG.warn("Queue [" + queueName + "] is of an unsupported queue provider type (not SQS)");
|
||||
return;
|
||||
}
|
||||
|
||||
SQSQueuePoller sqsQueuePoller = new SQSQueuePoller();
|
||||
sqsQueuePoller.setQueueMetaData(queue);
|
||||
sqsQueuePoller.setQueueProviderMetaData((SQSQueueProviderMetaData) queueProvider);
|
||||
sqsQueuePoller.setQInstance(qInstance);
|
||||
sqsQueuePoller.setSessionSupplier(QuartzScheduler.getInstance().getSessionSupplier());
|
||||
|
||||
/////////////
|
||||
// run it. //
|
||||
/////////////
|
||||
LOG.debug("Running SQS Queue poller", logPair("queueName", queueName));
|
||||
sqsQueuePoller.run();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void validateParams(SchedulableIdentity schedulableIdentity, Map<String, Object> paramMap) throws QException
|
||||
{
|
||||
String queueName = ValueUtils.getValueAsString(paramMap.get("queueName"));
|
||||
if(!StringUtils.hasContent(queueName))
|
||||
{
|
||||
throw (new QException("Missing scheduledJobParameter with key [queueName] in " + schedulableIdentity));
|
||||
}
|
||||
|
||||
QQueueMetaData queue = QContext.getQInstance().getQueue(queueName);
|
||||
if(queue == null)
|
||||
{
|
||||
throw (new QException("Unrecognized queueName [" + queueName + "] in " + schedulableIdentity));
|
||||
}
|
||||
|
||||
QQueueProviderMetaData queueProvider = QContext.getQInstance().getQueueProvider(queue.getProviderName());
|
||||
if(!(queueProvider instanceof SQSQueueProviderMetaData))
|
||||
{
|
||||
throw (new QException("Queue [" + queueName + "] is of an unsupported queue provider type (not SQS) in " + schedulableIdentity));
|
||||
}
|
||||
|
||||
if(queue.getSchedule() != null)
|
||||
{
|
||||
throw (new QException("Queue [" + queueName + "] has a schedule in its metaData - so it should not be dynamically scheduled via a scheduled job! " + schedulableIdentity));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getDescription(Map<String, Object> params)
|
||||
{
|
||||
return "Queue: " + params.get("queueName");
|
||||
}
|
||||
|
||||
}
|
@ -1,168 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler.schedulable.runner;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.polling.PollingAutomationPerTableRunner;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
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.automation.QAutomationProviderMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.QTableAutomationDetails;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzScheduler;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.identity.SchedulableIdentity;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Schedulable TableAutomations runner - e.g., how a table automations are run
|
||||
** by a scheduler.
|
||||
*******************************************************************************/
|
||||
public class SchedulableTableAutomationsRunner implements SchedulableRunner
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(SchedulableTableAutomationsRunner.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void run(Map<String, Object> params)
|
||||
{
|
||||
QInstance qInstance = QuartzScheduler.getInstance().getQInstance();
|
||||
|
||||
String tableName = ValueUtils.getValueAsString(params.get("tableName"));
|
||||
if(!StringUtils.hasContent(tableName))
|
||||
{
|
||||
LOG.warn("Missing tableName in params.");
|
||||
return;
|
||||
}
|
||||
|
||||
QTableMetaData table = qInstance.getTable(tableName);
|
||||
if(table == null)
|
||||
{
|
||||
LOG.warn("Unrecognized tableName [" + tableName + "]");
|
||||
return;
|
||||
}
|
||||
|
||||
AutomationStatus automationStatus = AutomationStatus.valueOf(ValueUtils.getValueAsString(params.get("automationStatus")));
|
||||
|
||||
/////////////
|
||||
// run it. //
|
||||
/////////////
|
||||
LOG.debug("Running table automations", logPair("tableName", tableName), logPair(""));
|
||||
QTableAutomationDetails automationDetails = table.getAutomationDetails();
|
||||
if(automationDetails == null)
|
||||
{
|
||||
LOG.warn("Could not find automationDetails for table for automations in QInstance", logPair("tableName", tableName));
|
||||
return;
|
||||
}
|
||||
|
||||
///////////////////////////////////
|
||||
// todo - sharded automations... //
|
||||
///////////////////////////////////
|
||||
PollingAutomationPerTableRunner.TableActionsInterface tableAction = new PollingAutomationPerTableRunner.TableActions(tableName, automationDetails, automationStatus);
|
||||
PollingAutomationPerTableRunner runner = new PollingAutomationPerTableRunner(qInstance, automationDetails.getProviderName(), QuartzScheduler.getInstance().getSessionSupplier(), tableAction);
|
||||
|
||||
/////////////
|
||||
// run it. //
|
||||
/////////////
|
||||
LOG.debug("Running Table Automations", logPair("tableName", tableName), logPair("automationStatus", automationStatus));
|
||||
runner.run();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void validateParams(SchedulableIdentity schedulableIdentity, Map<String, Object> paramMap) throws QException
|
||||
{
|
||||
String tableName = ValueUtils.getValueAsString(paramMap.get("tableName"));
|
||||
if(!StringUtils.hasContent(tableName))
|
||||
{
|
||||
throw (new QException("Missing scheduledJobParameter with key [tableName] in " + schedulableIdentity));
|
||||
}
|
||||
|
||||
String automationStatus = ValueUtils.getValueAsString(paramMap.get("automationStatus"));
|
||||
if(!StringUtils.hasContent(automationStatus))
|
||||
{
|
||||
throw (new QException("Missing scheduledJobParameter with key [automationStatus] in " + schedulableIdentity));
|
||||
}
|
||||
|
||||
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||
if(table == null)
|
||||
{
|
||||
throw (new QException("Unrecognized tableName [" + tableName + "] in " + schedulableIdentity));
|
||||
}
|
||||
|
||||
QTableAutomationDetails automationDetails = table.getAutomationDetails();
|
||||
if(automationDetails == null)
|
||||
{
|
||||
throw (new QException("Table [" + tableName + "] does not have automationDetails in " + schedulableIdentity));
|
||||
}
|
||||
|
||||
if(automationDetails.getSchedule() != null)
|
||||
{
|
||||
throw (new QException("Table [" + tableName + "] automationDetails has a schedule in its metaData - so it should not be dynamically scheduled via a scheduled job! " + schedulableIdentity));
|
||||
}
|
||||
|
||||
QAutomationProviderMetaData automationProvider = QContext.getQInstance().getAutomationProvider(automationDetails.getProviderName());
|
||||
|
||||
List<PollingAutomationPerTableRunner.TableActionsInterface> tableActionList = PollingAutomationPerTableRunner.getTableActions(QContext.getQInstance(), automationProvider.getName());
|
||||
for(PollingAutomationPerTableRunner.TableActionsInterface tableActions : tableActionList)
|
||||
{
|
||||
if(tableActions.status().name().equals(automationStatus))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// if we get out of the loop, it means we didn't find a matching status - so throw //
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
throw (new QException("Did not find table automation actions matching automationStatus [" + automationStatus + "] for table [" + tableName + "] in " + schedulableIdentity
|
||||
+ " (Found: " + tableActionList.stream().map(ta -> ta.status().name()).collect(Collectors.joining(",")) + ")"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getDescription(Map<String, Object> params)
|
||||
{
|
||||
return "TableAutomations: " + params.get("tableName") + "." + params.get("automationStatus");
|
||||
}
|
||||
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler.simple;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.context.CapturedContext;
|
||||
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.schedulable.SchedulableType;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.runner.SchedulableRunner;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class SimpleJobRunner implements Runnable
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(SimpleJobRunner.class);
|
||||
|
||||
private QInstance qInstance;
|
||||
private SchedulableType schedulableType;
|
||||
private Map<String, Object> params;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public SimpleJobRunner(QInstance qInstance, SchedulableType type, Map<String, Object> params)
|
||||
{
|
||||
this.qInstance = qInstance;
|
||||
this.schedulableType = type;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
CapturedContext capturedContext = QContext.capture();
|
||||
try
|
||||
{
|
||||
SimpleScheduler simpleScheduler = SimpleScheduler.getInstance(qInstance);
|
||||
QContext.init(qInstance, simpleScheduler.getSessionSupplier().get());
|
||||
|
||||
SchedulableRunner schedulableRunner = QCodeLoader.getAdHoc(SchedulableRunner.class, schedulableType.getRunner());
|
||||
schedulableRunner.run(params);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error running SimpleScheduler job", e, logPair("params", params));
|
||||
}
|
||||
finally
|
||||
{
|
||||
QContext.init(capturedContext);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,299 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.scheduler.simple;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
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.scheduleing.QScheduleMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.QSchedulerInterface;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.SchedulableType;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.identity.SchedulableIdentity;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QQQ Service (Singleton) that starts up repeating, scheduled jobs within QQQ.
|
||||
**
|
||||
** These include:
|
||||
** - Automation providers (which require polling)
|
||||
** - Queue pollers
|
||||
** - Scheduled processes.
|
||||
**
|
||||
** All of these jobs run using a "system session" - as defined by the sessionSupplier.
|
||||
*******************************************************************************/
|
||||
public class SimpleScheduler implements QSchedulerInterface
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(SimpleScheduler.class);
|
||||
|
||||
private static SimpleScheduler simpleScheduler = null;
|
||||
private final QInstance qInstance;
|
||||
private String schedulerName;
|
||||
|
||||
protected Supplier<QSession> sessionSupplier;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// for jobs that don't define a delay index, auto-stagger them, using this counter //
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
private int delayIndex = 0;
|
||||
|
||||
private Map<SchedulableIdentity, StandardScheduledExecutor> executors = new LinkedHashMap<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton constructor
|
||||
*******************************************************************************/
|
||||
private SimpleScheduler(QInstance qInstance)
|
||||
{
|
||||
this.qInstance = qInstance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton accessor
|
||||
*******************************************************************************/
|
||||
public static SimpleScheduler getInstance(QInstance qInstance)
|
||||
{
|
||||
if(simpleScheduler == null)
|
||||
{
|
||||
simpleScheduler = new SimpleScheduler(qInstance);
|
||||
}
|
||||
return (simpleScheduler);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
for(StandardScheduledExecutor executor : executors.values())
|
||||
{
|
||||
executor.start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void stopAsync()
|
||||
{
|
||||
for(StandardScheduledExecutor scheduledExecutor : executors.values())
|
||||
{
|
||||
scheduledExecutor.stopAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
for(StandardScheduledExecutor scheduledExecutor : executors.values())
|
||||
{
|
||||
scheduledExecutor.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void setupSchedulable(SchedulableIdentity schedulableIdentity, SchedulableType schedulableType, Map<String, Serializable> parameters, QScheduleMetaData schedule, boolean allowedToStart)
|
||||
{
|
||||
if(!allowedToStart)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SimpleJobRunner simpleJobRunner = new SimpleJobRunner(qInstance, schedulableType, new HashMap<>(parameters));
|
||||
StandardScheduledExecutor executor = new StandardScheduledExecutor(simpleJobRunner);
|
||||
executor.setName(schedulableIdentity.getIdentity());
|
||||
setScheduleInExecutor(schedule, executor);
|
||||
executors.put(schedulableIdentity, executor);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void setScheduleInExecutor(QScheduleMetaData schedule, StandardScheduledExecutor executor)
|
||||
{
|
||||
if(schedule.getRepeatMillis() != null)
|
||||
{
|
||||
executor.setDelayMillis(schedule.getRepeatMillis());
|
||||
}
|
||||
else
|
||||
{
|
||||
executor.setDelayMillis(1000 * schedule.getRepeatSeconds());
|
||||
}
|
||||
|
||||
if(schedule.getInitialDelayMillis() != null)
|
||||
{
|
||||
executor.setInitialDelayMillis(schedule.getInitialDelayMillis());
|
||||
}
|
||||
else if(schedule.getInitialDelaySeconds() != null)
|
||||
{
|
||||
executor.setInitialDelayMillis(1000 * schedule.getInitialDelaySeconds());
|
||||
}
|
||||
else
|
||||
{
|
||||
executor.setInitialDelayMillis(1000 * ++delayIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void unscheduleSchedulable(SchedulableIdentity schedulableIdentity, SchedulableType schedulableType)
|
||||
{
|
||||
StandardScheduledExecutor executor = executors.get(schedulableIdentity);
|
||||
if(executor != null)
|
||||
{
|
||||
LOG.info("Stopping job in simple scheduler", logPair("identity", schedulableIdentity));
|
||||
executors.remove(schedulableIdentity);
|
||||
executor.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void unscheduleAll() throws QException
|
||||
{
|
||||
for(Map.Entry<SchedulableIdentity, StandardScheduledExecutor> entry : new HashSet<>(executors.entrySet()))
|
||||
{
|
||||
StandardScheduledExecutor executor = executors.remove(entry.getKey());
|
||||
if(executor != null)
|
||||
{
|
||||
executor.stopAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for sessionSupplier
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setSessionSupplier(Supplier<QSession> sessionSupplier)
|
||||
{
|
||||
this.sessionSupplier = sessionSupplier;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for sessionSupplier
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Supplier<QSession> getSessionSupplier()
|
||||
{
|
||||
return sessionSupplier;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for managedExecutors
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<StandardScheduledExecutor> getExecutors()
|
||||
{
|
||||
return new ArrayList<>(executors.values());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for schedulerName
|
||||
*******************************************************************************/
|
||||
public String getSchedulerName()
|
||||
{
|
||||
return (this.schedulerName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for schedulerName
|
||||
*******************************************************************************/
|
||||
public void setSchedulerName(String schedulerName)
|
||||
{
|
||||
this.schedulerName = schedulerName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for schedulerName
|
||||
*******************************************************************************/
|
||||
public SimpleScheduler withSchedulerName(String schedulerName)
|
||||
{
|
||||
this.schedulerName = schedulerName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void unInit()
|
||||
{
|
||||
//////////////////////////////////////////////////
|
||||
// resetting the singleton should be sufficient //
|
||||
//////////////////////////////////////////////////
|
||||
simpleScheduler = null;
|
||||
}
|
||||
}
|
@ -362,7 +362,6 @@ public class JsonUtils
|
||||
switch(metaData.getType())
|
||||
{
|
||||
case INTEGER -> record.setValue(fieldName, jsonObjectToUse.optInt(backendName));
|
||||
case LONG -> record.setValue(fieldName, jsonObjectToUse.optLong(backendName));
|
||||
case DECIMAL -> record.setValue(fieldName, jsonObjectToUse.optBigDecimal(backendName, null));
|
||||
case BOOLEAN -> record.setValue(fieldName, jsonObjectToUse.optBoolean(backendName));
|
||||
case DATE_TIME ->
|
||||
|
@ -114,113 +114,6 @@ public class ValueUtils
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Type-safely make an Long from any Object.
|
||||
** null and empty-string inputs return null.
|
||||
** We try to strip away commas and decimals (as long as they are exactly equal to the int value)
|
||||
** We may throw if the input can't be converted to an integer.
|
||||
*******************************************************************************/
|
||||
public static Long getValueAsLong(Object value) throws QValueException
|
||||
{
|
||||
try
|
||||
{
|
||||
if(value == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
else if(value instanceof Integer i)
|
||||
{
|
||||
return Long.valueOf((i));
|
||||
}
|
||||
else if(value instanceof Long l)
|
||||
{
|
||||
return (l);
|
||||
}
|
||||
else if(value instanceof BigInteger b)
|
||||
{
|
||||
return (b.longValue());
|
||||
}
|
||||
else if(value instanceof Float f)
|
||||
{
|
||||
if(f.longValue() != f)
|
||||
{
|
||||
throw (new QValueException(f + " does not have an exact integer representation."));
|
||||
}
|
||||
return (f.longValue());
|
||||
}
|
||||
else if(value instanceof Double d)
|
||||
{
|
||||
if(d.longValue() != d)
|
||||
{
|
||||
throw (new QValueException(d + " does not have an exact integer representation."));
|
||||
}
|
||||
return (d.longValue());
|
||||
}
|
||||
else if(value instanceof BigDecimal bd)
|
||||
{
|
||||
return bd.longValueExact();
|
||||
}
|
||||
else if(value instanceof PossibleValueEnum<?> pve)
|
||||
{
|
||||
return getValueAsLong(pve.getPossibleValueId());
|
||||
}
|
||||
else if(value instanceof String s)
|
||||
{
|
||||
if(!StringUtils.hasContent(s))
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return (Long.parseLong(s));
|
||||
}
|
||||
catch(NumberFormatException nfe)
|
||||
{
|
||||
if(s.contains(","))
|
||||
{
|
||||
String sWithoutCommas = s.replaceAll(",", "");
|
||||
try
|
||||
{
|
||||
return (getValueAsLong(sWithoutCommas));
|
||||
}
|
||||
catch(Exception ignore)
|
||||
{
|
||||
throw (nfe);
|
||||
}
|
||||
}
|
||||
if(s.matches(".*\\.\\d+$"))
|
||||
{
|
||||
String sWithoutDecimal = s.replaceAll("\\.\\d+$", "");
|
||||
try
|
||||
{
|
||||
return (getValueAsLong(sWithoutDecimal));
|
||||
}
|
||||
catch(Exception ignore)
|
||||
{
|
||||
throw (nfe);
|
||||
}
|
||||
}
|
||||
throw (nfe);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw (new QValueException("Unsupported class " + value.getClass().getName() + " for converting to Long."));
|
||||
}
|
||||
}
|
||||
catch(QValueException qve)
|
||||
{
|
||||
throw (qve);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QValueException("Value [" + value + "] could not be converted to a Long.", e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Type-safely make an Integer from any Object.
|
||||
** null and empty-string inputs return null.
|
||||
@ -800,7 +693,6 @@ public class ValueUtils
|
||||
{
|
||||
case STRING, TEXT, HTML, PASSWORD -> getValueAsString(value);
|
||||
case INTEGER -> getValueAsInteger(value);
|
||||
case LONG -> getValueAsLong(value);
|
||||
case DECIMAL -> getValueAsBigDecimal(value);
|
||||
case BOOLEAN -> getValueAsBoolean(value);
|
||||
case DATE -> getValueAsLocalDate(value);
|
||||
|
@ -23,7 +23,6 @@ package com.kingsrook.qqq.backend.core.utils;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@ -57,16 +56,6 @@ public class YamlUtils
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String toYaml(Object object)
|
||||
{
|
||||
return toYaml(object, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String toYaml(Object object, Consumer<ObjectMapper> objectMapperCustomizer)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -77,10 +66,7 @@ public class YamlUtils
|
||||
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
|
||||
|
||||
if(objectMapperCustomizer != null)
|
||||
{
|
||||
objectMapperCustomizer.accept(objectMapper);
|
||||
}
|
||||
// todo? objectMapper.setFilterProvider(new OmitDefaultValuesFilterProvider());
|
||||
|
||||
objectMapper.findAndRegisterModules();
|
||||
return (objectMapper.writeValueAsString(object));
|
||||
|
@ -1,135 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.utils.aggregates;
|
||||
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Long version of data aggregator
|
||||
*******************************************************************************/
|
||||
public class LongAggregates implements AggregatesInterface<Long>
|
||||
{
|
||||
private int count = 0;
|
||||
// private Long countDistinct;
|
||||
private Long sum;
|
||||
private Long min;
|
||||
private Long max;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Add a new value to this aggregate set
|
||||
*******************************************************************************/
|
||||
public void add(Long input)
|
||||
{
|
||||
if(input == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
count++;
|
||||
|
||||
if(sum == null)
|
||||
{
|
||||
sum = input;
|
||||
}
|
||||
else
|
||||
{
|
||||
sum = sum + input;
|
||||
}
|
||||
|
||||
if(min == null || input < min)
|
||||
{
|
||||
min = input;
|
||||
}
|
||||
|
||||
if(max == null || input > max)
|
||||
{
|
||||
max = input;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public int getCount()
|
||||
{
|
||||
return (count);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Long getSum()
|
||||
{
|
||||
return (sum);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Long getMin()
|
||||
{
|
||||
return (min);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Long getMax()
|
||||
{
|
||||
return (max);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public BigDecimal getAverage()
|
||||
{
|
||||
if(this.count > 0)
|
||||
{
|
||||
return (BigDecimal.valueOf(this.sum.doubleValue() / (double) this.count));
|
||||
}
|
||||
else
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user