mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-19 05:30:43 +00:00
CE-936 - dynamic scheduling of records from ScheduledJob table; quartz re-pause if paused before rescheduling
This commit is contained in:
@ -23,10 +23,12 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query;
|
|||||||
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
|
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.QRecord;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -89,4 +91,18 @@ public class QueryOutput extends AbstractActionOutput implements Serializable
|
|||||||
return storage.getRecords();
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,461 @@
|
|||||||
|
/*
|
||||||
|
* 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.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(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,266 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,219 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
defineStandardScriptsWidgets(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void defineStandardScriptsWidgets(QInstance instance)
|
||||||
|
{
|
||||||
|
QJoinMetaData join = instance.getJoin(JOB_PARAMETER_JOIN_NAME);
|
||||||
|
instance.addWidget(ChildRecordListRenderer.widgetMetaDataBuilder(join)
|
||||||
|
.withCanAddChildRecord(true)
|
||||||
|
.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")))
|
||||||
|
.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()
|
||||||
|
.withAssociatedTableName(ScheduledJobParameter.TABLE_NAME)
|
||||||
|
.withJoinName(JOB_PARAMETER_JOIN_NAME)
|
||||||
|
.withName(ScheduledJobParameter.TABLE_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")))
|
||||||
|
.withSection(new QFieldSection("value", new QIcon().withName("dataset"), Tier.T2, List.of("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)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,278 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -28,12 +28,15 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import com.kingsrook.qqq.backend.core.actions.automation.polling.PollingAutomationPerTableRunner;
|
import com.kingsrook.qqq.backend.core.actions.automation.polling.PollingAutomationPerTableRunner;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||||
import com.kingsrook.qqq.backend.core.context.CapturedContext;
|
import com.kingsrook.qqq.backend.core.context.CapturedContext;
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
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.instances.QMetaDataVariableInterpreter;
|
||||||
import com.kingsrook.qqq.backend.core.logging.LogPair;
|
import com.kingsrook.qqq.backend.core.logging.LogPair;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
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.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
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.QInstance;
|
||||||
@ -44,11 +47,13 @@ import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueProviderMetaDa
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData;
|
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.metadata.scheduleing.QScheduleMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QSchedulerMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QSchedulerMetaData;
|
||||||
|
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.model.session.QSession;
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
|
||||||
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
||||||
import org.apache.commons.lang.NotImplementedException;
|
import org.apache.commons.lang.NotImplementedException;
|
||||||
|
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -112,21 +117,25 @@ public class QScheduleManager
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void start() throws QException
|
public void start() throws QException
|
||||||
{
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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);
|
||||||
|
schedulers.put(schedulerMetaData.getName(), scheduler);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// now, exist w/o setting up schedules and not starting schedules, if schedule manager isn't enabled here //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
if(!new QMetaDataVariableInterpreter().getBooleanFromPropertyOrEnvironment("qqq.scheduleManager.enabled", "QQQ_SCHEDULE_MANAGER_ENABLED", true))
|
if(!new QMetaDataVariableInterpreter().getBooleanFromPropertyOrEnvironment("qqq.scheduleManager.enabled", "QQQ_SCHEDULE_MANAGER_ENABLED", true))
|
||||||
{
|
{
|
||||||
LOG.info("Not starting ScheduleManager per settings.");
|
LOG.info("Not starting ScheduleManager per settings.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////
|
|
||||||
// initialize the scheduler(s) we're configured to use //
|
|
||||||
/////////////////////////////////////////////////////////
|
|
||||||
for(QSchedulerMetaData schedulerMetaData : qInstance.getSchedulers().values())
|
|
||||||
{
|
|
||||||
QSchedulerInterface scheduler = schedulerMetaData.initSchedulerInstance(qInstance, systemUserSessionSupplier);
|
|
||||||
schedulers.put(schedulerMetaData.getName(), scheduler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// ensure that everything which should be scheduled is scheduled, in the appropriate scheduler //
|
// ensure that everything which should be scheduled is scheduled, in the appropriate scheduler //
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -165,6 +174,26 @@ public class QScheduleManager
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private void setupSchedules()
|
private void setupSchedules()
|
||||||
{
|
{
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
// 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 //
|
// let the schedulers know we're starting this process //
|
||||||
/////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////
|
||||||
@ -192,6 +221,115 @@ public class QScheduleManager
|
|||||||
for(QProcessMetaData process : qInstance.getProcesses().values())
|
for(QProcessMetaData process : qInstance.getProcesses().values())
|
||||||
{
|
{
|
||||||
if(process.getSchedule() != null)
|
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)
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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 ops would clobber //
|
||||||
|
// scheduled-job record facts //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
if(!scheduledJob.getIsActive())
|
||||||
|
{
|
||||||
|
unscheduleScheduledJob(scheduledJob);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSchedulerInterface scheduler = getScheduler(scheduledJob.getSchedulerName());
|
||||||
|
|
||||||
|
QScheduleMetaData scheduleMetaData = new QScheduleMetaData();
|
||||||
|
scheduleMetaData.setCronExpression(scheduledJob.getCronExpression());
|
||||||
|
scheduleMetaData.setCronTimeZoneId(scheduledJob.getCronTimeZoneId());
|
||||||
|
|
||||||
|
switch(ScheduledJobType.getById(scheduledJob.getType()))
|
||||||
|
{
|
||||||
|
case PROCESS ->
|
||||||
|
{
|
||||||
|
Map<String, String> paramMap = scheduledJob.getJobParametersMap();
|
||||||
|
String processName = paramMap.get("processName");
|
||||||
|
QProcessMetaData process = qInstance.getProcess(processName);
|
||||||
|
|
||||||
|
// todo - variants... serial vs parallel?
|
||||||
|
scheduler.setupProcess(process, null, scheduleMetaData, true);
|
||||||
|
}
|
||||||
|
case QUEUE_PROCESSOR ->
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("ScheduledJob queue processors are not yet implemented...");
|
||||||
|
}
|
||||||
|
case TABLE_AUTOMATIONS ->
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("ScheduledJob table automations are not yet implemented...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void unscheduleScheduledJob(ScheduledJob scheduledJob)
|
||||||
|
{
|
||||||
|
QSchedulerInterface scheduler = getScheduler(scheduledJob.getSchedulerName());
|
||||||
|
|
||||||
|
switch(ScheduledJobType.getById(scheduledJob.getType()))
|
||||||
|
{
|
||||||
|
case PROCESS ->
|
||||||
|
{
|
||||||
|
Map<String, String> paramMap = scheduledJob.getJobParametersMap();
|
||||||
|
String processName = paramMap.get("processName");
|
||||||
|
QProcessMetaData process = qInstance.getProcess(processName);
|
||||||
|
scheduler.unscheduleProcess(process);
|
||||||
|
}
|
||||||
|
case QUEUE_PROCESSOR ->
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("ScheduledJob queue processors are not yet implemented...");
|
||||||
|
}
|
||||||
|
case TABLE_AUTOMATIONS ->
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("ScheduledJob table automations are not yet implemented...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void setupProcess(QProcessMetaData process)
|
||||||
{
|
{
|
||||||
QScheduleMetaData scheduleMetaData = process.getSchedule();
|
QScheduleMetaData scheduleMetaData = process.getSchedule();
|
||||||
if(process.getSchedule().getVariantBackend() == null || QScheduleMetaData.RunStrategy.SERIAL.equals(process.getSchedule().getVariantRunStrategy()))
|
if(process.getSchedule().getVariantBackend() == null || QScheduleMetaData.RunStrategy.SERIAL.equals(process.getSchedule().getVariantRunStrategy()))
|
||||||
@ -225,20 +363,6 @@ public class QScheduleManager
|
|||||||
LOG.error("Unsupported Schedule Run Strategy [" + process.getSchedule().getVariantRunStrategy() + "] was provided.");
|
LOG.error("Unsupported Schedule Run Strategy [" + process.getSchedule().getVariantRunStrategy() + "] was provided.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////
|
|
||||||
// todo - read dynamic schedules and schedule those things //
|
|
||||||
// e.g., user-scheduled processes, reports //
|
|
||||||
/////////////////////////////////////////////////////////////
|
|
||||||
// ScheduledJob scheduledJob = new ScheduledJob();
|
|
||||||
// setupScheduledJob(scheduledJob);
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////
|
|
||||||
// let the schedulers know we're done with this process //
|
|
||||||
//////////////////////////////////////////////////////////
|
|
||||||
schedulers.values().forEach(s -> s.endOfSetupSchedules());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,6 +52,11 @@ public interface QSchedulerInterface
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
void setupTableAutomation(QAutomationProviderMetaData automationProvider, PollingAutomationPerTableRunner.TableActionsInterface tableActions, QScheduleMetaData schedule, boolean allowedToStart);
|
void setupTableAutomation(QAutomationProviderMetaData automationProvider, PollingAutomationPerTableRunner.TableActionsInterface tableActions, QScheduleMetaData schedule, boolean allowedToStart);
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
void unscheduleProcess(QProcessMetaData process);
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -90,5 +95,4 @@ public interface QSchedulerInterface
|
|||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,11 @@ public class QuartzScheduler implements QSchedulerInterface
|
|||||||
|
|
||||||
private Scheduler scheduler;
|
private Scheduler scheduler;
|
||||||
|
|
||||||
|
private final static String GROUP_NAME_PROCESSES = "processes";
|
||||||
|
private final static String GROUP_NAME_SQS_QUEUES = "sqsQueues";
|
||||||
|
private final static String GROUP_NAME_TABLE_AUTOMATIONS = "tableAutomations";
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// create memoization objects for some quartz-query functions, that we'll only want to //
|
// 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. //
|
// use during our setup routine, when we'd query it many times over and over again. //
|
||||||
@ -95,6 +100,11 @@ public class QuartzScheduler implements QSchedulerInterface
|
|||||||
private Memoization<String, Set<JobKey>> jobKeyNamesMemoization = new Memoization<String, Set<JobKey>>()
|
private Memoization<String, Set<JobKey>> jobKeyNamesMemoization = new Memoization<String, Set<JobKey>>()
|
||||||
.withTimeout(Duration.of(0, ChronoUnit.SECONDS));
|
.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. //
|
// vars used during the setup routine, to figure out what jobs need deleted. //
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
@ -103,6 +113,7 @@ public class QuartzScheduler implements QSchedulerInterface
|
|||||||
private List<QuartzJobAndTriggerWrapper> scheduledJobsAtEndOfSetup = new ArrayList<>();
|
private List<QuartzJobAndTriggerWrapper> scheduledJobsAtEndOfSetup = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Constructor
|
** Constructor
|
||||||
**
|
**
|
||||||
@ -227,7 +238,7 @@ public class QuartzScheduler implements QSchedulerInterface
|
|||||||
jobData.put("backendVariantData", backendVariantData);
|
jobData.put("backendVariantData", backendVariantData);
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduleJob(process.getName(), "processes", QuartzRunProcessJob.class, jobData, schedule, allowedToStart);
|
scheduleJob(process.getName(), GROUP_NAME_PROCESSES, QuartzRunProcessJob.class, jobData, schedule, allowedToStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -239,8 +250,7 @@ public class QuartzScheduler implements QSchedulerInterface
|
|||||||
public void startOfSetupSchedules()
|
public void startOfSetupSchedules()
|
||||||
{
|
{
|
||||||
this.insideSetup = true;
|
this.insideSetup = true;
|
||||||
this.jobGroupNamesMemoization.setTimeout(Duration.ofSeconds(5));
|
this.allMemoizations.forEach(m -> m.setTimeout(Duration.ofSeconds(5)));
|
||||||
this.jobKeyNamesMemoization.setTimeout(Duration.ofSeconds(5));
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -253,6 +263,7 @@ public class QuartzScheduler implements QSchedulerInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -260,8 +271,7 @@ public class QuartzScheduler implements QSchedulerInterface
|
|||||||
public void endOfSetupSchedules()
|
public void endOfSetupSchedules()
|
||||||
{
|
{
|
||||||
this.insideSetup = false;
|
this.insideSetup = false;
|
||||||
this.jobGroupNamesMemoization.setTimeout(Duration.ofSeconds(0));
|
this.allMemoizations.forEach(m -> m.setTimeout(Duration.ofSeconds(0)));
|
||||||
this.jobKeyNamesMemoization.setTimeout(Duration.ofSeconds(0));
|
|
||||||
|
|
||||||
if(this.scheduledJobsAtStartOfSetup == null)
|
if(this.scheduledJobsAtStartOfSetup == null)
|
||||||
{
|
{
|
||||||
@ -343,6 +353,14 @@ public class QuartzScheduler implements QSchedulerInterface
|
|||||||
{
|
{
|
||||||
startAt.setTime(startAt.getTime() + scheduleMetaData.getInitialDelaySeconds() * 1000);
|
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 //
|
// Define a Trigger for the schedule //
|
||||||
@ -359,18 +377,6 @@ public class QuartzScheduler implements QSchedulerInterface
|
|||||||
///////////////////////////////////////
|
///////////////////////////////////////
|
||||||
addOrReplaceJobAndTrigger(jobKey, jobDetail, trigger);
|
addOrReplaceJobAndTrigger(jobKey, jobDetail, trigger);
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////
|
|
||||||
// either pause or resume, based on if allowed to start //
|
|
||||||
//////////////////////////////////////////////////////////
|
|
||||||
if(!allowedToStart)
|
|
||||||
{
|
|
||||||
pauseJob(jobKey.getName(), jobKey.getGroup());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
resumeJob(jobKey.getName(), jobKey.getGroup());
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// if we're inside the setup event (e.g., initial startup), then capture //
|
// 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. //
|
// this job as one that is currently active and should be kept. //
|
||||||
@ -404,10 +410,11 @@ public class QuartzScheduler implements QSchedulerInterface
|
|||||||
jobData.put("queueProviderName", queueProvider.getName());
|
jobData.put("queueProviderName", queueProvider.getName());
|
||||||
jobData.put("queueName", queue.getName());
|
jobData.put("queueName", queue.getName());
|
||||||
|
|
||||||
scheduleJob(queue.getName(), "sqsQueue", QuartzSqsPollerJob.class, jobData, schedule, allowedToStart);
|
scheduleJob(queue.getName(), GROUP_NAME_SQS_QUEUES, QuartzSqsPollerJob.class, jobData, schedule, allowedToStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -422,10 +429,22 @@ public class QuartzScheduler implements QSchedulerInterface
|
|||||||
jobData.put("tableName", tableActions.tableName());
|
jobData.put("tableName", tableActions.tableName());
|
||||||
jobData.put("automationStatus", tableActions.status().toString());
|
jobData.put("automationStatus", tableActions.status().toString());
|
||||||
|
|
||||||
scheduleJob(tableActions.tableName() + "." + tableActions.status(), "tableAutomations", QuartzTableAutomationsJob.class, jobData, schedule, allowedToStart);
|
scheduleJob(tableActions.tableName() + "." + tableActions.status(), GROUP_NAME_TABLE_AUTOMATIONS, QuartzTableAutomationsJob.class, jobData, schedule, allowedToStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void unscheduleProcess(QProcessMetaData process)
|
||||||
|
{
|
||||||
|
deleteJob(new JobKey(process.getName(), GROUP_NAME_PROCESSES));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -434,17 +453,42 @@ public class QuartzScheduler implements QSchedulerInterface
|
|||||||
boolean isJobAlreadyScheduled = isJobAlreadyScheduled(jobKey);
|
boolean isJobAlreadyScheduled = isJobAlreadyScheduled(jobKey);
|
||||||
if(isJobAlreadyScheduled)
|
if(isJobAlreadyScheduled)
|
||||||
{
|
{
|
||||||
this.scheduler.addJob(jobDetail, true);
|
boolean wasPaused = wasExistingJobPaused(jobKey);
|
||||||
|
|
||||||
|
this.scheduler.addJob(jobDetail, true); // note, true flag here replaces if already present.
|
||||||
this.scheduler.rescheduleJob(trigger.getKey(), trigger);
|
this.scheduler.rescheduleJob(trigger.getKey(), trigger);
|
||||||
LOG.info("Re-scheduled job: " + jobKey);
|
LOG.info("Re-scheduled job", logPair("jobKey", jobKey));
|
||||||
|
if(wasPaused)
|
||||||
|
{
|
||||||
|
LOG.info("Re-pausing job", logPair("jobKey", jobKey));
|
||||||
|
pauseJob(jobKey.getName(), jobKey.getGroup());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.scheduler.scheduleJob(jobDetail, trigger);
|
this.scheduler.scheduleJob(jobDetail, trigger);
|
||||||
LOG.info("Scheduled new job: " + jobKey);
|
LOG.info("Scheduled new job", logPair("jobKey", jobKey));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo - think about... clear memoization - but - when this is used in bulk, that's when we want the memo!
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -492,12 +536,15 @@ public class QuartzScheduler implements QSchedulerInterface
|
|||||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
if(isJobAlreadyScheduled(jobKey))
|
if(isJobAlreadyScheduled(jobKey))
|
||||||
{
|
{
|
||||||
return scheduler.deleteJob(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 //
|
// 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);
|
return (true);
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
@ -575,11 +622,12 @@ public class QuartzScheduler implements QSchedulerInterface
|
|||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
List<QuartzJobAndTriggerWrapper> queryQuartz() throws SchedulerException
|
List<QuartzJobAndTriggerWrapper> queryQuartz() throws SchedulerException
|
||||||
|
{
|
||||||
|
return queryQuartzMemoization.getResultThrowing(AnyKey.getInstance(), (x) ->
|
||||||
{
|
{
|
||||||
List<QuartzJobAndTriggerWrapper> rs = new ArrayList<>();
|
List<QuartzJobAndTriggerWrapper> rs = new ArrayList<>();
|
||||||
List<String> jobGroupNames = scheduler.getJobGroupNames();
|
|
||||||
|
|
||||||
for(String group : jobGroupNames)
|
for(String group : scheduler.getJobGroupNames())
|
||||||
{
|
{
|
||||||
Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.groupEquals(group));
|
Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.groupEquals(group));
|
||||||
for(JobKey jobKey : jobKeys)
|
for(JobKey jobKey : jobKeys)
|
||||||
@ -595,6 +643,7 @@ public class QuartzScheduler implements QSchedulerInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (rs);
|
return (rs);
|
||||||
|
}).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,9 +33,9 @@ import org.apache.commons.lang3.SerializationUtils;
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QuartzJobDetailsPostQueryCustomizer extends AbstractPostQueryCustomizer
|
public class QuartzJobDataPostQueryCustomizer extends AbstractPostQueryCustomizer
|
||||||
{
|
{
|
||||||
private static final QLogger LOG = QLogger.getLogger(QuartzJobDetailsPostQueryCustomizer.class);
|
private static final QLogger LOG = QLogger.getLogger(QuartzJobDataPostQueryCustomizer.class);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -55,10 +55,13 @@ public class QuartzJobDetailsPostQueryCustomizer extends AbstractPostQueryCustom
|
|||||||
// this field has a blob of essentially a serialized map - so, deserialize that, then convert to JSON //
|
// this field has a blob of essentially a serialized map - so, deserialize that, then convert to JSON //
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
byte[] value = record.getValueByteArray("jobData");
|
byte[] value = record.getValueByteArray("jobData");
|
||||||
|
if(value.length > 0)
|
||||||
|
{
|
||||||
Object deserialize = SerializationUtils.deserialize(value);
|
Object deserialize = SerializationUtils.deserialize(value);
|
||||||
String json = JsonUtils.toJson(deserialize);
|
String json = JsonUtils.toJson(deserialize);
|
||||||
record.setValue("jobData", json);
|
record.setValue("jobData", json);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
LOG.info("Error deserializing quartz job data", e);
|
LOG.info("Error deserializing quartz job data", e);
|
@ -39,6 +39,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaDa
|
|||||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
import com.kingsrook.qqq.backend.core.scheduler.QSchedulerInterface;
|
import com.kingsrook.qqq.backend.core.scheduler.QSchedulerInterface;
|
||||||
import com.kingsrook.qqq.backend.core.scheduler.SchedulerUtils;
|
import com.kingsrook.qqq.backend.core.scheduler.SchedulerUtils;
|
||||||
|
import org.apache.commons.lang.NotImplementedException;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -157,6 +158,17 @@ public class SimpleScheduler implements QSchedulerInterface
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void unscheduleProcess(QProcessMetaData process)
|
||||||
|
{
|
||||||
|
throw (new NotImplementedException("Unscheduling is not implemented in SimpleScheduler..."));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -177,7 +177,7 @@ class QuartzJobsProcessTest extends BaseTest
|
|||||||
List<QuartzJobAndTriggerWrapper> quartzJobAndTriggerWrappers = QuartzTestUtils.queryQuartz();
|
List<QuartzJobAndTriggerWrapper> quartzJobAndTriggerWrappers = QuartzTestUtils.queryQuartz();
|
||||||
new InsertAction().execute(new InsertInput("quartzTriggers").withRecord(new QRecord()
|
new InsertAction().execute(new InsertInput("quartzTriggers").withRecord(new QRecord()
|
||||||
.withValue("jobName", quartzJobAndTriggerWrappers.get(0).jobDetail().getKey().getName())
|
.withValue("jobName", quartzJobAndTriggerWrappers.get(0).jobDetail().getKey().getName())
|
||||||
.withValue("groupName", quartzJobAndTriggerWrappers.get(0).jobDetail().getKey().getGroup())
|
.withValue("jobGroup", quartzJobAndTriggerWrappers.get(0).jobDetail().getKey().getGroup())
|
||||||
));
|
));
|
||||||
|
|
||||||
input = new RunProcessInput();
|
input = new RunProcessInput();
|
||||||
@ -224,7 +224,7 @@ class QuartzJobsProcessTest extends BaseTest
|
|||||||
List<QuartzJobAndTriggerWrapper> quartzJobAndTriggerWrappers = QuartzTestUtils.queryQuartz();
|
List<QuartzJobAndTriggerWrapper> quartzJobAndTriggerWrappers = QuartzTestUtils.queryQuartz();
|
||||||
new InsertAction().execute(new InsertInput("quartzTriggers").withRecord(new QRecord()
|
new InsertAction().execute(new InsertInput("quartzTriggers").withRecord(new QRecord()
|
||||||
.withValue("jobName", quartzJobAndTriggerWrappers.get(0).jobDetail().getKey().getName())
|
.withValue("jobName", quartzJobAndTriggerWrappers.get(0).jobDetail().getKey().getName())
|
||||||
.withValue("groupName", quartzJobAndTriggerWrappers.get(0).jobDetail().getKey().getGroup())
|
.withValue("jobGroup", quartzJobAndTriggerWrappers.get(0).jobDetail().getKey().getGroup())
|
||||||
));
|
));
|
||||||
|
|
||||||
RunProcessInput input = new RunProcessInput();
|
RunProcessInput input = new RunProcessInput();
|
||||||
|
Reference in New Issue
Block a user