[Major] Use cron expressions to execute StrolchJobs

A strolch job can now be modelled in XML as follows:

   <Resource Id="reloadPolicies" Name="Reload Policies" Type="StrolchJob">
       <ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
           <Parameter Id="className" Name="Class Name" Type="String"
                   Value="li.strolch.policy.ReloadPoliciesJob"/>
           <Parameter Id="mode" Name="Job Mode" Type="String"
                   Interpretation="Enumeration" Uom="JobMode" Value="Recurring"/>
           <Parameter Id="startDate" Name="Job StartDate" Type="Date" Value="-"/>
           <Parameter Id="cron" Name="Cron" Type="String" Value="47 * * * *"/>
       </ParameterBag>
   </Resource>
This commit is contained in:
Robert von Burg 2020-05-02 21:57:25 +02:00
parent a389cc60f1
commit 6d4ae7ae33
10 changed files with 252 additions and 61 deletions

View File

@ -38,6 +38,11 @@
<artifactId>li.strolch.privilege</artifactId>
</dependency>
<dependency>
<groupId>ch.eitchnet</groupId>
<artifactId>cron</artifactId>
</dependency>
<!-- test -->
<dependency>
<groupId>junit</groupId>

View File

@ -0,0 +1,6 @@
package li.strolch.job;
public enum ConfigureMethod {
Programmatic,
Model
}

View File

@ -0,0 +1,17 @@
package li.strolch.job;
import li.strolch.agent.api.StrolchAgent;
import li.strolch.privilege.model.PrivilegeContext;
public class ReloadJobsJob extends StrolchJob {
public ReloadJobsJob(StrolchAgent agent, String name, JobMode mode) {
super(agent, name, mode);
}
@Override
protected void execute(PrivilegeContext ctx) throws Exception {
StrolchJobsHandler jobsHandler = getContainer().getComponent(StrolchJobsHandler.class);
jobsHandler.reloadJobs();
}
}

View File

@ -15,6 +15,7 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import com.google.gson.JsonObject;
import fc.cron.CronExpression;
import li.strolch.agent.api.ComponentContainer;
import li.strolch.agent.api.StrolchAgent;
import li.strolch.agent.api.StrolchComponent;
@ -32,6 +33,7 @@ import li.strolch.privilege.model.Restrictable;
import li.strolch.runtime.StrolchConstants;
import li.strolch.runtime.privilege.PrivilegedRunnable;
import li.strolch.utils.helper.ExceptionHelper;
import li.strolch.utils.time.PeriodDuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -43,10 +45,15 @@ public abstract class StrolchJob implements Runnable, Restrictable {
protected static final Logger logger = LoggerFactory.getLogger(StrolchJob.class);
private StrolchAgent agent;
private final StrolchAgent agent;
private final String name;
private JobMode mode;
private String cron;
private CronExpression cronExpression;
private String realmName;
private final JobMode mode;
private long initialDelay;
private TimeUnit initialDelayTimeUnit;
private long delay;
@ -61,37 +68,65 @@ public abstract class StrolchJob implements Runnable, Restrictable {
private ZonedDateTime lastExecution;
private Exception lastException;
public StrolchJob(StrolchAgent agent) {
private ConfigureMethod configureMethod;
private ZonedDateTime cronStartDate;
public StrolchJob(StrolchAgent agent, String name, JobMode jobMode) {
this.agent = agent;
this.mode = JobMode.Manual;
this.initialDelay = 0;
this.initialDelayTimeUnit = TimeUnit.SECONDS;
this.delay = 0;
this.delayTimeUnit = TimeUnit.SECONDS;
this.name = name;
this.mode = jobMode;
this.first = true;
}
public StrolchJob(StrolchAgent agent, JobMode jobMode, long initialDelay, TimeUnit initialDelayTimeUnit, long delay,
TimeUnit delayTimeUnit) {
this.agent = agent;
this.mode = jobMode;
public StrolchJob setConfigureMethod(ConfigureMethod configureMethod) {
this.configureMethod = configureMethod;
return this;
}
public ConfigureMethod getConfigureMethod() {
return this.configureMethod;
}
public String getCron() {
return this.cron;
}
public StrolchJob setCronExpression(String cron, ZonedDateTime startDate) {
this.cronExpression = CronExpression.createWithoutSeconds(cron);
this.cron = cron;
this.cronStartDate = startDate.isBefore(ZonedDateTime.now()) ? ZonedDateTime.now() : startDate;
this.initialDelay = 0;
this.initialDelayTimeUnit = null;
this.delay = 0;
this.delayTimeUnit = null;
return this;
}
public StrolchJob setDelay(long initialDelay, TimeUnit initialDelayTimeUnit, long delay, TimeUnit delayTimeUnit) {
this.initialDelay = initialDelay;
this.initialDelayTimeUnit = initialDelayTimeUnit;
this.delay = delay;
this.delayTimeUnit = delayTimeUnit;
this.first = true;
this.cronExpression = null;
this.cron = null;
this.cronStartDate = null;
return this;
}
public JobMode getMode() {
return this.mode;
}
public StrolchJob setMode(JobMode mode) {
this.mode = mode;
return this;
}
public String getName() {
return getClass().getSimpleName();
return this.name;
}
protected StrolchAgent getAgent() {
@ -218,7 +253,7 @@ public abstract class StrolchJob implements Runnable, Restrictable {
} catch (Exception e) {
this.running = false;
this.lastException = e;
logger.error("Execution of Job " + this.getClass().getSimpleName() + " failed.", e);
logger.error("Execution of Job " + getName() + " failed.", e);
OperationsLog operationsLog = getContainer().getComponent(OperationsLog.class);
if (operationsLog != null) {
@ -291,18 +326,58 @@ public abstract class StrolchJob implements Runnable, Restrictable {
cancel(false);
if (this.first) {
long millis = this.initialDelayTimeUnit.toMillis(this.initialDelay);
logger.info("First execution of " + getClass().getSimpleName() + " will be at " + ZonedDateTime.now()
.plus(millis, ChronoUnit.MILLIS).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
this.future = getScheduledExecutor().schedule(this, this.initialDelay, this.initialDelayTimeUnit);
if (this.cronExpression != null) {
ZonedDateTime executionTime;
try {
executionTime = this.cronExpression.nextTimeAfter(this.cronStartDate);
} catch (IllegalArgumentException e) {
logger.error("Can not schedule " + getName() + " after start date " + this.cronStartDate
+ " as no next time exists for cron expression " + this.cron);
return this;
}
logger.info("First execution of " + getName() + " will be at " + executionTime
.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
long delay = PeriodDuration.between(ZonedDateTime.now(), executionTime).toMillis();
this.future = getScheduledExecutor().schedule(this, delay, TimeUnit.MILLISECONDS);
} else {
long millis = this.initialDelayTimeUnit.toMillis(this.initialDelay);
logger.info("First execution of " + getName() + " will be at " + ZonedDateTime.now()
.plus(millis, ChronoUnit.MILLIS).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
this.future = getScheduledExecutor().schedule(this, this.initialDelay, this.initialDelayTimeUnit);
}
} else {
long millis = this.delayTimeUnit.toMillis(this.delay);
logger.info("Next execution of " + getClass().getSimpleName() + " will be at " + ZonedDateTime.now()
.plus(millis, ChronoUnit.MILLIS).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
this.future = getScheduledExecutor().schedule(this, this.delay, this.delayTimeUnit);
if (this.cronExpression != null) {
ZonedDateTime executionTime;
try {
executionTime = this.cronExpression.nextTimeAfter(this.lastExecution);
} catch (IllegalArgumentException e) {
logger.error("Can not schedule " + getName() + " after start date " + this.cronStartDate
+ " as no next time exists for cron expression " + this.cron);
return this;
}
logger.info("Next execution of " + getName() + " will be at " + executionTime
.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
long delay = PeriodDuration.between(ZonedDateTime.now(), executionTime).toMillis();
this.future = getScheduledExecutor().schedule(this, delay, TimeUnit.MILLISECONDS);
} else {
long millis = this.delayTimeUnit.toMillis(this.delay);
logger.info("Next execution of " + getName() + " will be at " + ZonedDateTime.now()
.plus(millis, ChronoUnit.MILLIS).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
this.future = getScheduledExecutor().schedule(this, this.delay, this.delayTimeUnit);
}
}
return this;
@ -326,14 +401,17 @@ public abstract class StrolchJob implements Runnable, Restrictable {
jsonObject.addProperty(Tags.Json.NAME, getName());
jsonObject.addProperty(Tags.Json.REALM, this.realmName);
jsonObject.addProperty("mode", this.mode.name());
jsonObject.addProperty("configureMethod", this.configureMethod == null ? "-" : this.configureMethod.name());
jsonObject.addProperty("cron", this.cron == null ? "-" : this.cron);
jsonObject.addProperty("initialDelay", this.initialDelay);
jsonObject.addProperty("initialDelayTimeUnit", this.initialDelayTimeUnit.name());
jsonObject.addProperty("initialDelayTimeUnit",
this.initialDelayTimeUnit == null ? "-" : this.initialDelayTimeUnit.name());
jsonObject.addProperty("delay", this.delay);
jsonObject.addProperty("delayTimeUnit", this.delayTimeUnit.name());
jsonObject.addProperty("delayTimeUnit", this.delayTimeUnit == null ? "-" : this.delayTimeUnit.name());
jsonObject.addProperty("running", this.running);
jsonObject.addProperty("totalDuration", formatMillisecondsDuration(totalDuration));
jsonObject.addProperty("lastDuration", formatMillisecondsDuration(lastDuration));
jsonObject.addProperty("totalDuration", formatMillisecondsDuration(this.totalDuration));
jsonObject.addProperty("lastDuration", formatMillisecondsDuration(this.lastDuration));
if (this.lastExecution == null) {
jsonObject.addProperty("lastExecution", "-");

View File

@ -1,5 +1,8 @@
package li.strolch.job;
import static java.lang.String.join;
import static li.strolch.runtime.StrolchConstants.*;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashMap;
@ -9,8 +12,9 @@ import java.util.Map;
import li.strolch.agent.api.ComponentContainer;
import li.strolch.agent.api.StrolchAgent;
import li.strolch.agent.api.StrolchComponent;
import li.strolch.model.parameter.DateParameter;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.privilege.model.Certificate;
import li.strolch.runtime.configuration.ComponentConfiguration;
/**
* The {@link StrolchJobsHandler} registers all {@link StrolchJob StrolchJobs}
@ -33,40 +37,105 @@ public class StrolchJobsHandler extends StrolchComponent {
}
@Override
public void initialize(ComponentConfiguration configuration) throws Exception {
this.jobs = new HashMap<>();
super.initialize(configuration);
public void start() throws Exception {
reloadJobs();
super.start();
}
private StrolchJob instantiateStrolchJob(Class<? extends StrolchJob> strolchJobClass) {
public void reloadJobs() throws Exception {
List<StrolchJob> jobs = new ArrayList<>();
// copy any already existing programmatically added jobs
if (this.jobs != null) {
this.jobs.values().forEach(value -> {
if (value.getConfigureMethod() == ConfigureMethod.Programmatic)
jobs.add(value);
});
}
String[] realmNames = getConfiguration().getStringArray("realms", join(",", getContainer().getRealmNames()));
runAsAgent(ctx -> {
for (String realmName : realmNames) {
try (StrolchTransaction tx = openTx(ctx.getCertificate(), realmName, true)) {
tx.streamResources(TYPE_STROLCH_JOB).forEach(jobRes -> {
String className = jobRes.getParameter(PARAM_CLASS_NAME, true).getValue();
String cron = jobRes.getParameter(PARAM_CRON, true).getValue();
DateParameter startDateP = jobRes.getParameter(PARAM_START_DATE, true);
JobMode mode = JobMode.valueOf(jobRes.getParameter(PARAM_MODE, true).getValue());
StrolchJob job = instantiateJob(className, jobRes.getName(), mode);
job.setConfigureMethod(ConfigureMethod.Model).setMode(mode)
.setCronExpression(cron, startDateP.toZonedDateTime());
jobs.add(job);
logger.info("Added job " + job.getName() + ": " + job.getCron() + " from model.");
});
}
}
});
StrolchAgent agent = getContainer().getAgent();
ReloadJobsJob reloadJobsJob = new ReloadJobsJob(agent, ReloadJobsJob.class.getSimpleName(), JobMode.Manual);
reloadJobsJob.setConfigureMethod(ConfigureMethod.Programmatic);
jobs.add(reloadJobsJob);
this.jobs = new HashMap<>();
jobs.forEach(job -> internalRegister(job).schedule());
}
@SuppressWarnings("unchecked")
private StrolchJob instantiateJob(String className, String name, JobMode mode) {
Class<StrolchJob> clazz;
try {
clazz = (Class<StrolchJob>) Class.forName(className);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("StrolchJob class " + className + " does not exist!");
}
return instantiateJob(clazz, name, mode);
}
private StrolchJob instantiateJob(Class<? extends StrolchJob> clazz, String name, JobMode mode) {
StrolchJob strolchJob;
try {
Constructor<? extends StrolchJob> constructor = strolchJobClass.getConstructor(StrolchAgent.class);
strolchJob = constructor.newInstance(getContainer().getAgent());
Constructor<? extends StrolchJob> constructor = clazz
.getConstructor(StrolchAgent.class, String.class, JobMode.class);
strolchJob = constructor.newInstance(getContainer().getAgent(), name, mode);
} catch (Exception e) {
throw new IllegalArgumentException("Failed to instantiate class " + strolchJobClass.getName(), e);
throw new IllegalArgumentException("Failed to instantiate job " + clazz.getName(), e);
}
return strolchJob;
}
public StrolchJob registerAndScheduleJob(Class<? extends StrolchJob> strolchJobClass) {
return register(instantiateStrolchJob(strolchJobClass)).schedule();
StrolchJob job = instantiateJob(strolchJobClass, strolchJobClass.getSimpleName(), JobMode.Manual);
job.setConfigureMethod(ConfigureMethod.Programmatic);
return register(job).schedule();
}
public StrolchJob register(Class<? extends StrolchJob> strolchJobClass) {
return register(instantiateStrolchJob(strolchJobClass));
StrolchJob job = instantiateJob(strolchJobClass, strolchJobClass.getSimpleName(), JobMode.Manual);
job.setConfigureMethod(ConfigureMethod.Programmatic);
return register(job);
}
public StrolchJob register(StrolchJob strolchJob) {
if (this.jobs.containsKey(strolchJob.getName()))
throw new IllegalArgumentException("Job " + strolchJob.getName() + " is already registered!");
this.jobs.put(strolchJob.getName(), strolchJob);
public StrolchJob register(StrolchJob job) {
return internalRegister(job.setConfigureMethod(ConfigureMethod.Programmatic));
}
return strolchJob;
private StrolchJob internalRegister(StrolchJob job) {
if (this.jobs.containsKey(job.getName()))
throw new IllegalArgumentException("Job " + job.getName() + " is already registered!");
this.jobs.put(job.getName(), job);
return job;
}
public List<StrolchJob> getJobs(Certificate cert, String source) {
getContainer().getPrivilegeHandler().validate(cert, source).assertHasPrivilege(StrolchJobsHandler.class.getName());
getContainer().getPrivilegeHandler().validate(cert, source)
.assertHasPrivilege(StrolchJobsHandler.class.getName());
return new ArrayList<>(this.jobs.values());
}

View File

@ -1,13 +1,14 @@
package li.strolch.policy;
import li.strolch.agent.api.StrolchAgent;
import li.strolch.job.JobMode;
import li.strolch.job.StrolchJob;
import li.strolch.privilege.model.PrivilegeContext;
public class ReloadPoliciesJob extends StrolchJob {
public ReloadPoliciesJob(StrolchAgent agent) {
super(agent);
public ReloadPoliciesJob(StrolchAgent agent, String name, JobMode mode) {
super(agent, name, mode);
}
@Override

View File

@ -1,13 +1,14 @@
package li.strolch.policy;
import li.strolch.agent.api.StrolchAgent;
import li.strolch.job.JobMode;
import li.strolch.job.StrolchJob;
import li.strolch.privilege.model.PrivilegeContext;
public class ReloadPrivilegeHandlerJob extends StrolchJob {
public ReloadPrivilegeHandlerJob(StrolchAgent agent) {
super(agent);
public ReloadPrivilegeHandlerJob(StrolchAgent agent, String name, JobMode jobMode) {
super(agent, name, jobMode);
}
@Override

View File

@ -1,12 +1,12 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -27,7 +27,7 @@ import li.strolch.privilege.handler.PrivilegeHandler;
* @author Robert von Burg <eitch@eitchnet.ch>
*/
@SuppressWarnings("nls")
public class StrolchConstants {
public class StrolchConstants extends StrolchModelConstants {
public static final String STROLCH_ENV = "strolch.env";
public static final String STROLCH_PATH = "strolch.path";
@ -71,6 +71,13 @@ public class StrolchConstants {
*/
public static final String ROLE_STROLCH_ADMIN = StrolchModelConstants.ROLE_STROLCH_ADMIN;
public static final String TYPE_STROLCH_JOB = "StrolchJob";
public static final String PARAM_CLASS_NAME = "className";
public static final String PARAM_CRON = "cron";
public static final String PARAM_START_DATE = "startDate";
public static final String PARAM_MODE = "mode";
public static String makeRealmKey(String realmName, String key) {
String realmKey = key;
if (!realmName.equals(DEFAULT_REALM))
@ -80,7 +87,7 @@ public class StrolchConstants {
/**
* Constants used for Privilege management, configuration, etc.
*
*
* @author Robert von Burg <eitch@eitchnet.ch>
*/
public static class StrolchPrivilegeConstants {
@ -111,10 +118,10 @@ public class StrolchConstants {
public static final String PRIVILEGE_INVALIDATE_SESSION = "InvalidateSession";
public static final String PRIVILEGE_GET_SESSION = "GetSession";
public static final String PRIVILEGE_ADD_PREFIX= "Add";
public static final String PRIVILEGE_UPDATE_PREFIX= "Update";
public static final String PRIVILEGE_REMOVE_PREFIX= "Remove";
public static final String PRIVILEGE_GET_PREFIX= "Get";
public static final String PRIVILEGE_ADD_PREFIX = "Add";
public static final String PRIVILEGE_UPDATE_PREFIX = "Update";
public static final String PRIVILEGE_REMOVE_PREFIX = "Remove";
public static final String PRIVILEGE_GET_PREFIX = "Get";
public static final String INTERNAL = StrolchModelConstants.INTERNAL;
}

View File

@ -12,13 +12,14 @@ import li.strolch.privilege.model.PrivilegeContext;
public class ArchiveExecutedActivitiesJob extends StrolchJob {
public ArchiveExecutedActivitiesJob(StrolchAgent agent) {
super(agent, JobMode.Manual, 0, TimeUnit.MINUTES, 0, TimeUnit.MINUTES);
public ArchiveExecutedActivitiesJob(StrolchAgent agent, String name, JobMode jobMode) {
super(agent, name, jobMode);
}
public ArchiveExecutedActivitiesJob(StrolchAgent agent, JobMode jobMode, long initialDelay,
TimeUnit initialDelayTimeUnit, long delay, TimeUnit delayTimeUnit) {
super(agent, jobMode, initialDelay, initialDelayTimeUnit, delay, delayTimeUnit);
super(agent, ArchiveExecutedActivitiesJob.class.getSimpleName(), jobMode);
setDelay(initialDelay, initialDelayTimeUnit, delay, delayTimeUnit);
}
@Override

View File

@ -93,6 +93,7 @@
<jaxb.api.version>2.4.0-b180830.0359</jaxb.api.version>
<csv.version>1.7</csv.version>
<sax.version>2.0.1</sax.version>
<cron.version>1.6.2</cron.version>
<!-- test time dependencies -->
<junit.version>4.12</junit.version>
@ -216,6 +217,11 @@
</dependency>
<!-- Miscellaneous -->
<dependency>
<groupId>ch.eitchnet</groupId>
<artifactId>cron</artifactId>
<version>${cron.version}</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>