All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.datarouter.job.BaseTriggerGroup Maven / Gradle / Ivy

The newest version!
/*
 * Copyright © 2009 HotPads ([email protected])
 *
 * 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.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.datarouter.job;

import java.time.Duration;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

import org.apache.logging.log4j.core.util.CronExpression;

import io.datarouter.job.detached.DetachedJobResource;
import io.datarouter.job.lock.TriggerLockConfig;
import io.datarouter.job.scheduler.JobPackage;
import io.datarouter.job.util.CronExpressionTool;
import io.datarouter.plugin.PluginConfigKey;
import io.datarouter.plugin.PluginConfigType;
import io.datarouter.plugin.PluginConfigValue;
import io.datarouter.storage.tag.Tag;
import io.datarouter.util.ComparableTool;
import io.datarouter.util.string.StringTool;

public abstract class BaseTriggerGroup implements PluginConfigValue{

	public static final PluginConfigKey KEY = new PluginConfigKey<>(
			"triggerGroup",
			PluginConfigType.CLASS_LIST);

	private final String categoryName;
	private final List subGroups;
	private final List jobPackages;
	private final Map> requestTriggeredJobs;
	private final ZoneId zoneId;
	public final Tag tag;

	public BaseTriggerGroup(String categoryName, ZoneId zoneId){
		this(categoryName, Tag.APP, zoneId);
	}

	public BaseTriggerGroup(String categoryName, Tag tag, ZoneId zoneId){
		this.categoryName = categoryName;
		this.zoneId = zoneId;
		this.subGroups = new ArrayList<>();
		this.jobPackages = new ArrayList<>();
		this.requestTriggeredJobs = new HashMap<>();
		this.tag = tag;
	}

	protected void include(BaseTriggerGroup triggerGroup){
		subGroups.add(triggerGroup);
	}

	/*----------------- register -----------------*/

	protected void registerContinuous(
			String cronString,
			Supplier shouldRunSupplier,
			Class jobClass){
		registerParallel(cronString, shouldRunSupplier, jobClass);
	}

	protected void registerParallel(
			String cronString,
			Supplier shouldRunSupplier,
			Class jobClass){
		CronExpression cronExpression = CronExpressionTool.parse(cronString, zoneId);
		validateParallelTriggerCron(jobClass, cronExpression);
		jobPackages.add(JobPackage.createParallel(categoryName, cronExpression, shouldRunSupplier, jobClass));
	}

	protected void registerLocked(
			String cronString,
			Supplier shouldRunSupplier,
			Class jobClass,
			boolean warnOnReachingDuration){
		CronExpression cronExpression = CronExpressionTool.parse(cronString, zoneId);
		Duration lockDuration = CronExpressionTool.durationBetweenNextTwoTriggers(cronExpression);
		registerLockedWithName(
				cronString,
				shouldRunSupplier,
				jobClass,
				lockName(jobClass),
				lockDuration,
				warnOnReachingDuration);
	}

	protected void registerLockedWithName(
			String cronString,
			Supplier shouldRunSupplier,
			Class jobClass,
			String lockName,
			Duration lockDuration,
			boolean warnOnReachingDuration){
		CronExpression cronExpression = CronExpressionTool.parse(cronString, zoneId);
		validateLockedTriggerCron(jobClass, cronExpression);
		var triggerLockConfig = new TriggerLockConfig(
				lockName,
				cronExpression,
				lockDuration,
				warnOnReachingDuration);
		jobPackages.add(JobPackage.createWithLock(
				categoryName,
				cronExpression,
				shouldRunSupplier,
				jobClass,
				triggerLockConfig));
	}

	protected void registerDetached(
			String cronString,
			Supplier shouldRunSupplier,
			Class jobClass,
			boolean warnOnReachingDuration){
		registerDetached(cronString, shouldRunSupplier, jobClass, warnOnReachingDuration, null);
	}

	protected void registerDetached(
				String cronString,
				Supplier shouldRunSupplier,
				Class jobClass,
				boolean warnOnReachingDuration,
				DetachedJobResource detachedJobResource){
		CronExpression cronExpression = CronExpressionTool.parse(cronString, zoneId);
		validateLockedTriggerCron(jobClass, cronExpression);
		Duration lockDuration = CronExpressionTool.durationBetweenNextTwoTriggers(cronExpression);
		var triggerLockConfig = new TriggerLockConfig(
				lockName(jobClass),
				cronExpression,
				lockDuration,
				warnOnReachingDuration);
		jobPackages.add(JobPackage.createDetached(
				categoryName,
				cronExpression,
				shouldRunSupplier,
				jobClass,
				triggerLockConfig,
				detachedJobResource));
	}

	protected void registerRequestTriggered(
			String persistentString,
			Class jobClass){
		requestTriggeredJobs.put(persistentString, jobClass);
	}

	/*----------------- misc --------------------*/

	public List getJobPackages(){
		List jobPackagesIncludingSubGroups = new ArrayList<>();
		subGroups.stream()
				.map(BaseTriggerGroup::getJobPackages)
				.forEach(jobPackagesIncludingSubGroups::addAll);
		jobPackagesIncludingSubGroups.addAll(jobPackages);
		return jobPackagesIncludingSubGroups;
	}

	public Map> getRequestTriggeredJobs(){
		return requestTriggeredJobs;
	}

	public static String lockName(Class jobClass){
		return jobClass.getSimpleName();
	}

	/*------------------ validate -----------------*/

	private void validateParallelTriggerCron(
			Class jobClass,
			CronExpression cronExpression){
		validateNoRepeatAfterWildcard(jobClass, cronExpression.getCronExpression());
		validateRepeatInterval(jobClass, cronExpression.getCronExpression());
	}

	private void validateLockedTriggerCron(
			Class jobClass,
			CronExpression cronExpression){
		validateNoRepeatAfterWildcard(jobClass, cronExpression.getCronExpression());
		validateRepeatInterval(jobClass, cronExpression.getCronExpression());
		validateMinTimeBetweenLockedTriggers(jobClass, cronExpression);
	}

	private void validateNoRepeatAfterWildcard(
			Class jobClass,
			String cronString){
		if(cronString.contains("/") && StringTool.getStringBeforeFirstOccurrence('/', cronString).contains("*")){
			String message = String.format(
					"cron %s for %s in %s contains a * before a /.  Please use exact times.",
					cronString,
					jobClass.getSimpleName(),
					getClass().getSimpleName());
			throw new IllegalArgumentException(message);
		}
	}

	private void validateRepeatInterval(
			Class jobClass,
			String cronString){
		String[] tokens = cronString.split(" ");
		String problem = null;
		if(CronExpressionTool.hasUnevenInterval(60, tokens[0])){
			problem = "second interval is not a factor of 60";
		}
		if(CronExpressionTool.hasUnevenInterval(60, tokens[1])){
			problem = "minute interval is not a factor of 60";
		}
		if(CronExpressionTool.hasUnevenInterval(24, tokens[2])){
			problem = "hour interval is not a factor of 24";
		}
		if(problem == null){
			return;
		}
		String message = String.format(
				"cron %s for %s in %s %s.",
				cronString,
				jobClass.getSimpleName(),
				getClass().getSimpleName(),
				problem);
		throw new IllegalArgumentException(message);
	}

	private void validateMinTimeBetweenLockedTriggers(
			Class jobClass,
			CronExpression cronExpression){
		Duration timeBetween = CronExpressionTool.durationBetweenNextTwoTriggers(cronExpression);
		if(ComparableTool.lt(timeBetween, TriggerLockConfig.MIN_PERIOD_BETWEEN_LOCKED_TRIGGERS)){
			String message = String.format(
					"cron %s for %s in %s is too frequent with %ss between triggers.  Please "
							+ "reschedule locked triggers at least %s seconds apart",
					cronExpression.getCronExpression(),
					jobClass.getSimpleName(),
					getClass().getSimpleName(),
					timeBetween.getSeconds(),
					TriggerLockConfig.MIN_PERIOD_BETWEEN_LOCKED_TRIGGERS.getSeconds());
			throw new IllegalArgumentException(message);
		}
	}

	@Override
	public PluginConfigKey getKey(){
		return KEY;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy