/**
* Licensed to JumpMind Inc under one or more contributor
* license agreements. See the NOTICE file distributed
* with this work for additional information regarding
* copyright ownership. JumpMind Inc licenses this file
* to you under the GNU General Public License, version 3.0 (GPLv3)
* (the "License"); you may not use this file except in compliance
* with the License.
*
* You should have received a copy of the GNU General Public License,
* version 3.0 (GPLv3) along with this library; if not, see
* <http://www.gnu.org/licenses/>.
*
* 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.jumpmind.symmetric.job;
import java.util.Date;
import java.util.concurrent.ScheduledFuture;
import org.apache.commons.lang.StringUtils;
import org.jumpmind.symmetric.ISymmetricEngine;
import org.jumpmind.symmetric.common.Constants;
import org.jumpmind.symmetric.common.ParameterConstants;
import org.jumpmind.symmetric.model.Lock;
import org.jumpmind.symmetric.service.IParameterService;
import org.jumpmind.util.RandomTimeSlot;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedMetric;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
@ManagedResource(description = "The management interface for a job")
abstract public class AbstractJob implements Runnable, IJob {
protected final Logger log = LoggerFactory.getLogger(getClass());
private String jobName;
private boolean requiresRegistration = true;
private boolean paused = false;
private Date lastFinishTime;
private boolean running = false;
private long lastExecutionTimeInMs;
private long totalExecutionTimeInMs;
private long numberOfRuns;
private boolean started;
private boolean hasNotRegisteredMessageBeenLogged = false;
private ThreadPoolTaskScheduler taskScheduler;
private ScheduledFuture<?> scheduledJob;
private RandomTimeSlot randomTimeSlot;
private boolean autoStartConfigured;
protected ISymmetricEngine engine;
protected AbstractJob(String jobName, boolean requiresRegistration, boolean autoStartRequired,
ISymmetricEngine engine, ThreadPoolTaskScheduler taskScheduler) {
this.engine = engine;
this.taskScheduler = taskScheduler;
this.jobName = jobName;
this.requiresRegistration = requiresRegistration;
this.autoStartConfigured = autoStartRequired;
IParameterService parameterService = engine.getParameterService();
this.randomTimeSlot = new RandomTimeSlot(parameterService.getExternalId(),
parameterService.getInt(ParameterConstants.JOB_RANDOM_MAX_START_TIME_MS));
}
public boolean isAutoStartConfigured() {
return autoStartConfigured;
}
public void start() {
if (this.scheduledJob == null && engine != null
&& !engine.getClusterService().isInfiniteLocked(getClusterLockName())) {
String cronExpression = engine.getParameterService().getString(jobName + ".cron", null);
int timeBetweenRunsInMs = engine.getParameterService().getInt(
jobName + ".period.time.ms", -1);
if (!StringUtils.isBlank(cronExpression)) {
log.info("Starting {} with cron expression: {}", jobName, cronExpression);
this.scheduledJob = taskScheduler.schedule(this, new CronTrigger(cronExpression));
started = true;
} else {
int startDelay = randomTimeSlot.getRandomValueSeededByExternalId();
long currentTimeMillis = System.currentTimeMillis();
long lastRunTime = currentTimeMillis - timeBetweenRunsInMs;
Lock lock = engine.getClusterService().findLocks().get(getClusterLockName());
if (lock != null && lock.getLastLockTime() != null) {
long newRunTime = lock.getLastLockTime().getTime();
if (lastRunTime < newRunTime) {
lastRunTime = newRunTime;
}
}
Date firstRun = new Date(lastRunTime + timeBetweenRunsInMs + startDelay);
log.info("Starting {} on periodic schedule: every {}ms with the first run at {}", new Object[] {jobName,
timeBetweenRunsInMs, firstRun});
if (timeBetweenRunsInMs > 0) {
this.scheduledJob = taskScheduler.scheduleWithFixedDelay(this,
firstRun, timeBetweenRunsInMs);
started = true;
} else {
log.error("Failed to schedule this job, {}", jobName);
}
}
}
}
public boolean stop() {
boolean success = false;
if (this.scheduledJob != null) {
success = this.scheduledJob.cancel(true);
this.scheduledJob = null;
if (success) {
log.info("The {} job has been cancelled", jobName);
started = false;
} else {
log.warn("Failed to cancel this job, {}", jobName);
}
}
return success;
}
public String getName() {
return jobName;
}
@ManagedOperation(description = "Run this job is it isn't already running")
public boolean invoke() {
return invoke(true);
}
public boolean invoke(boolean force) {
IParameterService parameterService = engine.getParameterService();
boolean ran = false;
try {
if (engine == null) {
log.info("Could not find a reference to the SymmetricEngine from {}", jobName);
} else {
if (!Thread.interrupted()) {
MDC.put("engineName", engine.getEngineName());
if (engine.isStarted()) {
if (!paused || force) {
if (!running) {
running = true;
synchronized (this) {
ran = true;
long startTime = System.currentTimeMillis();
try {
if (!requiresRegistration
|| (requiresRegistration && engine
.getRegistrationService()
.isRegisteredWithServer())) {
hasNotRegisteredMessageBeenLogged = false;
if (parameterService.is(ParameterConstants.SYNCHRONIZE_ALL_JOBS)) {
synchronized (AbstractJob.class) {
doJob(force);
}
} else {
doJob(force);
}
} else {
if (!hasNotRegisteredMessageBeenLogged) {
log.info(
"Did not run the {} job because the engine is not registered.",
getName());
hasNotRegisteredMessageBeenLogged = true;
}
}
} finally {
lastFinishTime = new Date();
long endTime = System.currentTimeMillis();
lastExecutionTimeInMs = endTime - startTime;
totalExecutionTimeInMs += lastExecutionTimeInMs;
if (lastExecutionTimeInMs > Constants.LONG_OPERATION_THRESHOLD) {
engine.getStatisticManager().addJobStats(jobName,
startTime, endTime, 0);
}
numberOfRuns++;
running = false;
}
}
}
}
} else {
log.info("The engine is not currently started.");
}
} else {
log.warn("This thread was interrupted. Not executing the job until the interrupted status has cleared");
}
}
} catch (final Throwable ex) {
log.error("", ex);
}
return ran;
}
/*
* This method is called from the job
*/
public void run() {
MDC.put("engineName", engine != null ? engine.getEngineName() : "unknown");
invoke(false);
}
abstract void doJob(boolean force) throws Exception;
@ManagedOperation(description = "Pause this job")
public void pause() {
setPaused(true);
}
@ManagedOperation(description = "Resume the job")
public void unpause() {
setPaused(false);
}
public void setPaused(boolean paused) {
this.paused = paused;
}
@ManagedAttribute(description = "If true, this job has been paused")
public boolean isPaused() {
return paused;
}
@ManagedAttribute(description = "If true, this job has been started")
public boolean isStarted() {
return started;
}
@ManagedMetric(description = "The amount of time this job spent in execution during it's last run")
public long getLastExecutionTimeInMs() {
return lastExecutionTimeInMs;
}
@ManagedAttribute(description = "The last time this job completed execution")
public Date getLastFinishTime() {
return lastFinishTime;
}
@ManagedAttribute(description = "If true, the job is already running")
public boolean isRunning() {
return running;
}
@ManagedMetric(description = "The number of times this job has been run during the lifetime of the JVM")
public long getNumberOfRuns() {
return numberOfRuns;
}
@ManagedMetric(description = "The total amount of time this job has spent in execution during the lifetime of the JVM")
public long getTotalExecutionTimeInMs() {
return totalExecutionTimeInMs;
}
@ManagedMetric(description = "The total amount of time this job has spend in execution during the lifetime of the JVM")
public long getAverageExecutionTimeInMs() {
if (numberOfRuns > 0) {
return totalExecutionTimeInMs / numberOfRuns;
} else {
return 0;
}
}
@ManagedAttribute(description = "If set, this is the cron expression that governs when the job will run")
public String getCronExpression() {
return engine.getParameterService().getString(jobName + ".cron", null);
}
@ManagedAttribute(description = "If the cron expression isn't set. This is the amount of time that will pass before the periodic job runs again.")
public long getTimeBetweenRunsInMs() {
return engine.getParameterService().getInt(jobName + ".period.time.ms", -1);
}
}