org.lastaflute.job.cron4j.Cron4jNow Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lasta-job Show documentation
Show all versions of lasta-job Show documentation
Job Scheduling for LastaFlute
/*
* Copyright 2015-2017 the original author or authors.
*
* 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 org.lastaflute.job.cron4j;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.Supplier;
import org.dbflute.optional.OptionalThing;
import org.dbflute.util.DfTypeUtil;
import org.dbflute.util.Srl;
import org.lastaflute.job.LaJobHistory;
import org.lastaflute.job.LaJobRunner;
import org.lastaflute.job.LaSchedulingNow;
import org.lastaflute.job.cron4j.Cron4jCron.CronRegistrationType;
import org.lastaflute.job.exception.JobNotFoundException;
import org.lastaflute.job.key.LaJobKey;
import org.lastaflute.job.key.LaJobNote;
import org.lastaflute.job.key.LaJobUnique;
import org.lastaflute.job.log.JobChangeLog;
import org.lastaflute.job.subsidiary.CronConsumer;
import org.lastaflute.job.subsidiary.JobConcurrentExec;
import org.lastaflute.job.subsidiary.JobSubIdentityAttr;
import org.lastaflute.job.subsidiary.NeighborConcurrentGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author jflute
* @since 0.2.0 (2016/01/11 Monday)
*/
public class Cron4jNow implements LaSchedulingNow {
// ===================================================================================
// Definition
// ==========
private static final Logger logger = LoggerFactory.getLogger(Cron4jNow.class);
// ===================================================================================
// Attribute
// =========
protected final Cron4jScheduler cron4jScheduler;
protected final LaJobRunner jobRunner;
protected final Supplier currentTime;
protected final boolean frameworkDebug;
protected final Map jobKeyJobMap = new ConcurrentHashMap();
protected final List jobOrderedList = new CopyOnWriteArrayList(); // same lifecycle as jobKeyJobMap
protected final Map jobUniqueJobMap = new ConcurrentHashMap();
protected final Map cron4jTaskJobMap = new ConcurrentHashMap();
protected final Map neighborConcurrentMap = new ConcurrentHashMap();
protected int incrementedJobNumber;
// ===================================================================================
// Constructor
// ===========
public Cron4jNow(Cron4jScheduler cron4jScheduler, LaJobRunner jobRunner, Supplier currentTime, boolean frameworkDebug) {
this.cron4jScheduler = cron4jScheduler;
this.jobRunner = jobRunner;
this.currentTime = currentTime;
this.frameworkDebug = frameworkDebug;
}
// ===================================================================================
// Save Job
// ========
/**
* @param cron4jTask (NotNull)
* @param subIdentityAttr The optional identity attributes of job, e.g. jobTitle, jobUnique. (NotNull)
* @param triggeringJobKeyList The list of job key triggering me. (NotNull, EmptyAllowed)
* @param cron4jId The ID auto-generated by cron4j. (NotNull, EmptyAllowed: when non-scheduling)
* @return The new-created job to be saved in this object. (NotNull)
*/
public synchronized Cron4jJob saveJob(Cron4jTask cron4jTask, JobSubIdentityAttr subIdentityAttr, List triggeringJobKeyList,
OptionalThing cron4jId) {
assertArgumentNotNull("cron4jTask", cron4jTask);
final LaJobKey jobKey = generateJobKey(cron4jTask);
final Cron4jJob cron4jJob = createCron4jJob(jobKey, subIdentityAttr, triggeringJobKeyList, cron4jId, cron4jTask);
assertDuplicateJobKey(jobKey);
jobKeyJobMap.put(jobKey, cron4jJob);
jobOrderedList.add(cron4jJob);
subIdentityAttr.getJobUnique().ifPresent(uniqueCode -> {
assertDuplicateUniqueCode(jobKey, uniqueCode);
jobUniqueJobMap.put(uniqueCode, cron4jJob);
});
cron4jTaskJobMap.put(cron4jTask, cron4jJob); // task is unique in lasta-job world
return cron4jJob;
}
// -----------------------------------------------------
// JobKey
// ------
protected LaJobKey generateJobKey(Cron4jTask cron4jTask) {
if (incrementedJobNumber == Integer.MAX_VALUE) { // no no no, no way! but just in case
incrementedJobNumber = 1;
} else { // mainly here
++incrementedJobNumber;
}
return createJobKey(buildJobKey(cron4jTask, incrementedJobNumber));
}
protected String buildJobKey(Cron4jTask cron4jTask, int jobNumber) {
return Srl.initUncap(cron4jTask.getJobType().getSimpleName()) + "_" + jobNumber;
}
protected LaJobKey createJobKey(String jobKey) {
return LaJobKey.of(jobKey);
}
// -----------------------------------------------------
// Cron4jJob
// ---------
protected Cron4jJob createCron4jJob(LaJobKey jobKey, JobSubIdentityAttr subIdentityAttr, List triggeringJobKeyList,
OptionalThing cron4jId, Cron4jTask cron4jTask) {
final Cron4jJob job = newCron4jJob(jobKey, subIdentityAttr.getJobNote(), subIdentityAttr.getJobUnique(),
cron4jId.map(id -> Cron4jId.of(id)), cron4jTask, this);
triggeringJobKeyList.forEach(triggeringJobKey -> {
findJobByKey(triggeringJobKey).alwaysPresent(triggeringJob -> {
triggeringJob.registerNext(jobKey);
});
});
return job;
}
protected Cron4jJob newCron4jJob(LaJobKey jobKey, OptionalThing jobNote, OptionalThing jobUnique,
OptionalThing cron4jId, Cron4jTask cron4jTask, Cron4jNow cron4jNow) {
return new Cron4jJob(jobKey, jobNote, jobUnique, cron4jId, cron4jTask, cron4jNow);
}
// -----------------------------------------------------
// Assert
// ------
protected void assertDuplicateJobKey(LaJobKey jobKey) {
if (jobKeyJobMap.containsKey(jobKey)) { // no way because of auto-generated, just in case
throw new IllegalStateException("Duplicate job key: " + jobKey + " existing=" + jobKeyJobMap);
}
}
protected void assertDuplicateUniqueCode(LaJobKey jobKey, LaJobUnique jobUnique) {
if (jobUniqueJobMap.containsKey(jobUnique)) { // application mistake
// simple message #for_now
throw new IllegalStateException("Duplicate job unique: " + jobKey + " existing=" + jobUniqueJobMap);
}
}
protected void assertDuplicateTask(Cron4jTask cron4jTask) {
if (cron4jTaskJobMap.containsKey(cron4jTask)) { // no way just in case
throw new IllegalStateException("Duplicate task: " + cron4jTask + " existing=" + jobKeyJobMap);
}
}
// ===================================================================================
// Find Job
// ========
@Override
public OptionalThing findJobByKey(LaJobKey jobKey) {
assertArgumentNotNull("jobKey", jobKey);
final Cron4jJob found = jobKeyJobMap.get(jobKey);
return OptionalThing.ofNullable(found, () -> {
String msg = "Not found the job by the key: " + jobKey + " existing=" + jobKeyJobMap.keySet();
throw new JobNotFoundException(msg);
});
}
@Override
public OptionalThing findJobByUniqueOf(LaJobUnique jobUnique) {
assertArgumentNotNull("jobUnique", jobUnique);
final Cron4jJob found = jobUniqueJobMap.get(jobUnique);
return OptionalThing.ofNullable(found, () -> {
String msg = "Not found the job by the unique code: " + jobUnique + " existing=" + jobUniqueJobMap.keySet();
throw new JobNotFoundException(msg);
});
}
public OptionalThing findJobByTask(Cron4jTask task) {
assertArgumentNotNull("task", task);
final Cron4jJob found = cron4jTaskJobMap.get(task);
return OptionalThing.ofNullable(found, () -> {
String msg = "Not found the job by the task: " + task + " existing=" + cron4jTaskJobMap.keySet();
throw new JobNotFoundException(msg);
});
}
@Override
public List getJobList() {
return Collections.unmodifiableList(jobOrderedList);
}
public List getCron4jJobList() {
return Collections.unmodifiableList(jobOrderedList);
}
// ===================================================================================
// Register Job
// ============
@Override
public synchronized void schedule(CronConsumer oneArgLambda) {
assertArgumentNotNull("oneArgLambda", oneArgLambda);
oneArgLambda.consume(createCron4jCron());
}
protected Cron4jCron createCron4jCron() {
return new Cron4jCron(cron4jScheduler, jobRunner, this, CronRegistrationType.CHANGE, currentTime, isFrameworkDebug());
}
// ===================================================================================
// Clear Job
// =========
/**
* Clear disappeared jobs from job list if it exists.
*/
public synchronized void clearDisappearedJob() {
getCron4jJobList().stream().filter(job -> job.isDisappeared()).forEach(job -> {
final LaJobKey jobKey = job.getJobKey();
jobKeyJobMap.remove(jobKey);
jobOrderedList.remove(job);
job.getJobUnique().ifPresent(jobUnique -> jobUniqueJobMap.remove(jobUnique));
cron4jTaskJobMap.remove(job.getCron4jTask());
});
}
// ===================================================================================
// Job History
// ===========
@Override
public List searchJobHistoryList() {
final Supplier> nativeSearcher = () -> Cron4jJobHistory.list();
return jobRunner.getHistoryHook().map(hook -> {
return hook.hookList(nativeSearcher);
}).orElseGet(() -> {
return nativeSearcher.get();
});
}
// ===================================================================================
// Neighbor Concurrent
// ===================
@Override
public void setupNeighborConcurrent(String groupName, JobConcurrentExec concurrentExec, Set jobKeySet) {
assertArgumentNotNull("groupName", groupName);
if (groupName.trim().isEmpty()) {
throw new IllegalArgumentException("The argument 'groupName' should not be empty: [" + groupName + "]");
}
assertArgumentNotNull("concurrentExec", concurrentExec);
assertArgumentNotNull("jobKeySet", jobKeySet);
if (jobKeySet.size() <= 1) {
throw new IllegalArgumentException("The specified jobs should be two or more: " + jobKeySet);
}
synchronized (neighborConcurrentMap) {
checkDuplicateNeighborConcurrentGroup(groupName);
final Object groupPreparingLock = new Object();
final Object groupRunningLock = new Object();
final CopyOnWriteArraySet safeSet = new CopyOnWriteArraySet(jobKeySet);
final NeighborConcurrentGroup group =
new NeighborConcurrentGroup(groupName, concurrentExec, safeSet, groupPreparingLock, groupRunningLock);
neighborConcurrentMap.put(groupName, group);
safeSet.stream().map(jobKey -> findJobByKey(jobKey).get()).forEach(job -> { // or job not found
job.registerNeighborConcurrent(groupName, group);
});
}
}
protected void checkDuplicateNeighborConcurrentGroup(String groupName) {
final NeighborConcurrentGroup existingGroup = neighborConcurrentMap.get(groupName);
if (existingGroup != null) {
String msg = "The groupName already exists: specified=" + groupName + ", existing=" + existingGroup;
throw new IllegalArgumentException(msg);
}
}
// ===================================================================================
// Destroy Schedule
// ================
@Override
public synchronized void destroy() {
if (JobChangeLog.isEnabled()) {
JobChangeLog.log("#job ...Destroying scheduler completely: jobs={} scheduler={}", jobKeyJobMap.size(), cron4jScheduler);
}
// not use AsyncManager here, because not frequent call, keep no dependency to core
new Thread(() -> { // to release synchronized lock to avoid deadlock
try {
cron4jScheduler.stop();
} catch (RuntimeException e) {
final String msg = "#job Failed to stop jobs: jobs={} scheduler={}";
if (JobChangeLog.isEnabled()) {
JobChangeLog.log(msg, jobKeyJobMap.size(), cron4jScheduler, e);
} else { // just in case
logger.info(msg, jobKeyJobMap.size(), cron4jScheduler, e);
}
}
Cron4jJobHistory.clear();
}).start();
}
// ===================================================================================
// Framework Debug
// ===============
protected boolean isFrameworkDebug() {
return frameworkDebug;
}
// ===================================================================================
// Small Helper
// ============
protected void assertArgumentNotNull(String variableName, Object value) {
if (variableName == null) {
throw new IllegalArgumentException("The variableName should not be null.");
}
if (value == null) {
throw new IllegalArgumentException("The argument '" + variableName + "' should not be null.");
}
}
// ===================================================================================
// Basic Override
// ==============
@Override
public String toString() {
return DfTypeUtil.toClassTitle(this) + ":{scheduled=" + jobKeyJobMap.size() + "}@" + Integer.toHexString(hashCode());
}
// ===================================================================================
// Accessor
// ========
public Cron4jScheduler getCron4jScheduler() {
return cron4jScheduler;
}
public LaJobRunner getJobRunner() {
return jobRunner;
}
public Map getJobKeyJobMap() {
return Collections.unmodifiableMap(jobKeyJobMap);
}
public Map getJobUniqueJobMap() {
return Collections.unmodifiableMap(jobUniqueJobMap);
}
public Map getTaskJobMap() {
return Collections.unmodifiableMap(cron4jTaskJobMap);
}
}