com.qwazr.scheduler.SchedulerManager Maven / Gradle / Ivy
Show all versions of qwazr-scheduler Show documentation
/*
* Copyright 2015-2017 Emmanuel Keller / QWAZR
*
* 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 com.qwazr.scheduler;
import com.qwazr.cluster.ClusterManager;
import com.qwazr.scripts.ScriptManager;
import com.qwazr.scripts.ScriptRunStatus;
import com.qwazr.scripts.ScriptServiceBuilder;
import com.qwazr.scripts.ScriptServiceInterface;
import com.qwazr.server.ApplicationBuilder;
import com.qwazr.server.GenericServerBuilder;
import com.qwazr.server.ServerException;
import com.qwazr.server.configuration.ServerConfiguration;
import com.qwazr.utils.LoggerUtils;
import com.qwazr.utils.ObjectMappers;
import com.qwazr.utils.concurrent.ReadWriteLock;
import org.apache.commons.lang3.StringUtils;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder;
import org.quartz.impl.DirectSchedulerFactory;
import javax.ws.rs.core.Response.Status;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.logging.Level;
import java.util.logging.Logger;
public class SchedulerManager implements Closeable {
private static final Logger LOGGER = LoggerUtils.getLogger(SchedulerManager.class);
public static final String QWAZR_SCHEDULER_MAX_THREADS = "QWAZR_SCHEDULER_MAX_THREADS";
public static final int DEFAULT_MAX_THREADS = 200;
private final String myAddress;
private final ScriptServiceBuilder scriptServiceBuilder;
private final ClusterManager clusterManager;
private final Scheduler globalScheduler;
private final Map> schedulerStatusMap;
private final ReadWriteLock statusMapLock;
private final ReadWriteLock mapLock;
private final Map> schedulerFileMap;
private volatile Map schedulerMap;
private final SchedulerServiceInterface service;
public SchedulerManager(final ExecutorService executorService, final ClusterManager clusterManager,
final ScriptManager scriptManager, final Integer maxThreads, final Collection etcFiles)
throws IOException, SchedulerException, ServerException {
scriptServiceBuilder = new ScriptServiceBuilder(executorService, clusterManager, scriptManager);
this.clusterManager = clusterManager;
this.myAddress = clusterManager == null ? null : clusterManager.getService().getStatus().me;
statusMapLock = ReadWriteLock.stamped();
mapLock = ReadWriteLock.stamped();
schedulerMap = null;
schedulerFileMap = new HashMap<>();
schedulerStatusMap = new HashMap<>();
final DirectSchedulerFactory schedulerFactory = DirectSchedulerFactory.getInstance();
schedulerFactory.createVolatileScheduler(maxThreads == null ? DEFAULT_MAX_THREADS : maxThreads);
globalScheduler = schedulerFactory.getScheduler();
globalScheduler.getContext().put(SchedulerManager.class.getName(), this);
globalScheduler.start();
Runtime.getRuntime().addShutdownHook(new Thread(this::close));
if (etcFiles != null)
etcFiles.forEach(this::loadSchedulerConf);
service = new SchedulerServiceImpl(this);
}
@Override
public synchronized void close() {
try {
if (!globalScheduler.isShutdown())
globalScheduler.shutdown();
} catch (SchedulerException e) {
LOGGER.log(Level.SEVERE, e, e::getMessage);
}
}
public static Integer getMaxThreadConfiguration(ServerConfiguration configuration) {
return configuration.getIntegerProperty(QWAZR_SCHEDULER_MAX_THREADS, DEFAULT_MAX_THREADS);
}
public SchedulerManager registerContextAttribute(final GenericServerBuilder builder) {
builder.contextAttribute(this);
return this;
}
public SchedulerManager registerWebService(final ApplicationBuilder builder) {
builder.singletons(service);
return this;
}
public SchedulerServiceInterface getService() {
return service;
}
TreeMap getSchedulers() {
final TreeMap map = new TreeMap<>();
final Map scMap = schedulerMap;
if (scMap == null)
return map;
scMap.forEach((name, schedulerDef) -> map.put(name, myAddress + "/schedulers/" + name));
return map;
}
SchedulerDefinition getScheduler(final String schedulerName) throws IOException {
final Map scMap = schedulerMap;
final SchedulerDefinition schedulerDefinition = scMap == null ? null : scMap.get(schedulerName);
if (schedulerDefinition == null)
throw new ServerException(Status.NOT_FOUND, "Scheduler not found: " + schedulerName);
return schedulerDefinition;
}
List getStatusList(final String schedulerName) throws IOException {
return statusMapLock.readEx(() -> schedulerStatusMap.get(schedulerName));
}
private void checkSchedulerCron(final String schedulerName, final SchedulerDefinition scheduler)
throws SchedulerException {
final JobDetail job = JobBuilder.newJob(SchedulerJob.class).withIdentity(schedulerName).build();
if (scheduler.enabled != null && scheduler.enabled) {
final CronScheduleBuilder cronBuilder = CronScheduleBuilder.cronSchedule(scheduler.cron);
if (!StringUtils.isEmpty(scheduler.time_zone))
cronBuilder.inTimeZone(TimeZone.getTimeZone(scheduler.time_zone));
final TriggerBuilder triggerBuilder =
TriggerBuilder.newTrigger().withIdentity(schedulerName).withSchedule(cronBuilder).forJob(job);
final CronTrigger trigger = triggerBuilder.build();
synchronized (globalScheduler) {
globalScheduler.scheduleJob(job, trigger);
}
} else {
synchronized (globalScheduler) {
globalScheduler.deleteJob(job.getKey());
}
}
}
List executeScheduler(final String schedulerName, final SchedulerDefinition scheduler)
throws IOException, ServerException, URISyntaxException {
if (!clusterManager.isLeader(SchedulerServiceInterface.SERVICE_NAME, scheduler.group))
return Collections.emptyList();
LOGGER.info(() -> "execute " + schedulerName + " / " + scheduler.script_path);
final long startTime = System.currentTimeMillis();
final ScriptServiceInterface scriptService = scriptServiceBuilder.getActive(scheduler.group);
if (scriptService == null)
return null;
final List statusList =
scriptService.runScriptVariables(scheduler.script_path, scheduler.group, scheduler.rule,
scheduler.variables);
if (statusList == null)
return null;
final List statusList2 = ScriptRunStatus.cloneSchedulerResultList(statusList, startTime);
statusMapLock.write(() -> schedulerStatusMap.put(schedulerName, statusList2));
return statusList2;
}
List executeScheduler(final String schedulerName)
throws IOException, ServerException, URISyntaxException {
return executeScheduler(schedulerName, getScheduler(schedulerName));
}
private void loadSchedulerConf(final File jsonFile) {
try {
final SchedulerConfiguration schedulerConfiguration =
ObjectMappers.JSON.readValue(jsonFile, SchedulerConfiguration.class);
if (schedulerConfiguration == null || schedulerConfiguration.schedulers == null) {
unloadSchedulerConf(jsonFile);
return;
}
LOGGER.info(() -> "Load Scheduler configuration file: " + jsonFile.getAbsolutePath());
mapLock.writeEx(() -> {
schedulerFileMap.put(jsonFile, schedulerConfiguration.schedulers);
buildSchedulerMap();
});
} catch (IOException | SchedulerException e) {
LOGGER.log(Level.SEVERE, e, e::getMessage);
}
}
private void unloadSchedulerConf(File jsonFile) {
try {
mapLock.writeEx(() -> {
final Map schedulerDefMap = schedulerFileMap.remove(jsonFile);
if (schedulerDefMap == null)
return;
LOGGER.info(() -> "Unload Scheduler configuration file: " + jsonFile.getAbsolutePath());
buildSchedulerMap();
});
} catch (SchedulerException e) {
LOGGER.log(Level.SEVERE, e, e::getMessage);
}
}
private void buildSchedulerMap() throws SchedulerException {
synchronized (globalScheduler) {
globalScheduler.clear();
}
final Map map = new HashMap<>();
schedulerFileMap.forEach((file, schedulerDefMap) -> map.putAll(schedulerDefMap));
final List removeKeys = new ArrayList<>();
// Remove the no more existing jobs status
statusMapLock.read(() -> {
schedulerStatusMap.forEach((name, scriptRunStatuses) -> {
if (!map.containsKey(name))
removeKeys.add(name);
});
removeKeys.forEach(schedulerStatusMap::remove);
});
// Set the volatile map
schedulerMap = map;
// Reschedule the jobs
schedulerMap.forEach((name, schedulerDefinition) -> {
try {
checkSchedulerCron(name, schedulerDefinition);
} catch (SchedulerException e) {
LOGGER.log(Level.SEVERE, e, () -> "Error on scheduler " + name + ": " + e.getMessage());
}
});
}
}