Compare commits

..

2 Commits

129 changed files with 874 additions and 9040 deletions

View File

@ -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>

View File

@ -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());

View File

@ -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));
}
}
}

View File

@ -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);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -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));
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -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;
}

View File

@ -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);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -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);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -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();
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -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);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -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)
{

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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));
}
}

View File

@ -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));

View File

@ -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);

View File

@ -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() + "'");

View File

@ -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")

View File

@ -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());
}
/////////////////////////////////////////////////////////////////////////

View File

@ -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())

View File

@ -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()));
}
}
}

View File

@ -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())

View File

@ -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()));
}
}

View File

@ -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)
{

View File

@ -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));

View File

@ -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);
}
}

View File

@ -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.

View File

@ -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"
*******************************************************************************/

View File

@ -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());
}
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -48,17 +48,6 @@ public class CollectedLogMessage
/*******************************************************************************
**
*******************************************************************************/
@Override
public String toString()
{
return "CollectedLogMessage{level=" + level + ", message='" + message + '\'' + ", exception=" + exception + '}';
}
/*******************************************************************************
** Getter for message
*******************************************************************************/

View File

@ -60,11 +60,6 @@ public interface QueryOrGetInputInterface
QBackendTransaction getTransaction();
/*******************************************************************************
**
*******************************************************************************/
String getTableName();
/*******************************************************************************
** Setter for transaction
*******************************************************************************/

View File

@ -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();
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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)));
}
/*******************************************************************************
**

View File

@ -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;
}
}

View File

@ -29,11 +29,6 @@ package com.kingsrook.qqq.backend.core.model.metadata;
public interface TopLevelMetaDataInterface
{
/*******************************************************************************
**
*******************************************************************************/
String getName();
/*******************************************************************************
**
*******************************************************************************/

View File

@ -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);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -55,17 +55,6 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
/*******************************************************************************
**
*******************************************************************************/
@Override
public String getName()
{
return "Branding";
}
/*******************************************************************************
** Getter for companyName
**

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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
}

View File

@ -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);
}
}

View File

@ -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")

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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)));
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}
}

View File

@ -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())

View File

@ -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);

View File

@ -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")

View File

@ -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")

View File

@ -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);
///////////////////////////////////////////////////////////////////////////////////////

View File

@ -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);
}
}

View File

@ -113,6 +113,10 @@ public class ExtractViaQueryStep extends AbstractExtractStep
{
queryInput.setShouldFetchHeavyFields(true);
}
if(runBackendStepInput.getValuePrimitiveBoolean(StreamedETLWithFrontendProcess.FIELD_INCLUDE_ASSOCIATIONS))
{
queryInput.setIncludeAssociations(true);
}
customizeInputPreQuery(queryInput);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -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 //
/////////////////////
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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));
}
}
}

View File

@ -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));
}
}
}

View File

@ -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)
{
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}
}

View File

@ -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));
}
}
}

View File

@ -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));
}
}
}

View File

@ -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));
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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");
}
}

View File

@ -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");
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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 ->

View File

@ -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);

View File

@ -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));

View File

@ -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