
de.tsl2.nano.service.schedule.AbstractJobScheduleServiceBean Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tsl2.nano.serviceaccess Show documentation
Show all versions of tsl2.nano.serviceaccess Show documentation
TSL2 JEE Service Access (Generic Services for Entity Access, JEE File-System-Connector, Generic Featuring, Job-Scheduling, BeanContainer, Batch, Comfortable Bean Query Definitions, JAAS, Authentification, Authorization, )
The newest version!
/*
* File: $HeadURL$
* Id : $Id$
*
* created by: Thomas Schneider
* created on: Jan 11, 2012
*
* Copyright: (c) Thomas Schneider, all rights reserved
*/
package de.tsl2.nano.service.schedule;
import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedList;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.ejb.EJBException;
import javax.ejb.NoMoreTimeoutsException;
import javax.ejb.NoSuchObjectLocalException;
import javax.ejb.ScheduleExpression;
import javax.ejb.Singleton;
import javax.ejb.Timeout;
import javax.ejb.Timer;
import javax.ejb.TimerConfig;
import javax.ejb.TimerHandle;
import javax.ejb.TimerService;
import org.apache.commons.logging.Log;
import de.tsl2.nano.core.ManagedException;
import de.tsl2.nano.core.log.LogFactory;
import de.tsl2.nano.core.util.FileUtil;
import de.tsl2.nano.core.util.ListSet;
import de.tsl2.nano.core.util.StringUtil;
/**
* see {@link IJobScheduleLocalService}.
*
* @author Thomas Schneider
* @version $Revision$
*/
@Singleton
public abstract class AbstractJobScheduleServiceBean implements
IJobScheduleLocalService,
IJobScheduleService {
private static final Log LOG = LogFactory.getLog(AbstractJobScheduleServiceBean.class);
/** all current persisted jobs to read last start, stop and status */
private Collection> jobs = new ArrayList>();
/** serialized job-history, containing all starts, stopps and status of all jobs - to be filtered */
private Collection jobHistory = new ListSet();
private static final String FILE_HISTORY = "jobhistory.ser";
private final boolean persistent = true;
@Resource
protected TimerService timerService;
@SuppressWarnings("unchecked")
@PostConstruct
protected void initializePersistedJobs() {
Collection timers = timerService.getTimers();
for (Timer timer : timers) {
//load persisted jobs after server crash or shutdown
if (timer.getInfo() instanceof Job) {
Job job = (Job) timer.getInfo();
job.setTimerHandle(timer);
//check for previous vm-crashes - then the job wasn't stopped
if (job.isRunning()) {
job.setAsStopped(new Exception("job wasn't stopped regularly. Perhaps the server crashed while running!"));
}
jobs.add(job);
}
}
//should be done through file-connector
if (new File(FILE_HISTORY).exists()) {
jobHistory = (Collection) FileUtil.load(FILE_HISTORY);
}
}
/**
* {@inheritDoc}
*/
@Override
public TimerHandle createScheduleJob(String name,
Date time,
Serializable context,
Serializable user,
boolean stopOnError,
boolean stopOnConcurrent,
boolean expire,
Collection callbacks) {
Job> job = initializeJob(name, callbacks, context, user, stopOnError, stopOnConcurrent);
Timer timer;
if (expire) {
timer = timerService.createSingleActionTimer(time, new TimerConfig(job, persistent));
} else {
timer = timerService.createTimer(time, job);
}
return job.setTimerHandle(timer);
}
/**
* {@inheritDoc}
*/
@Override
public TimerHandle createScheduleJob(String name, long time, boolean stopOnError, RUNNABLE... callbacks) {
Job> job = initializeJob(name, Arrays.asList(callbacks), null, null, true, true);
final Timer timer = timerService.createTimer(time, job);
return job.setTimerHandle(timer);
}
/**
* {@inheritDoc}
*/
@Override
public TimerHandle createScheduleJob(String name,
ScheduleExpression scheduleExpression,
boolean stopOnError,
RUNNABLE... callbacks) {
return createScheduleJob(name, scheduleExpression, null, null, stopOnError, true, callbacks);
}
/**
* {@inheritDoc}
*/
@Override
public TimerHandle createScheduleJob(String name,
ScheduleExpression scheduleExpression,
Serializable context,
Serializable user,
boolean stopOnError,
boolean stopOnConcurrent,
RUNNABLE... callbacks) {
Job> job = initializeJob(name, Arrays.asList(callbacks), context, user, stopOnError, stopOnConcurrent);
final Timer timer = timerService.createCalendarTimer(scheduleExpression, new TimerConfig(job, persistent));
return job.setTimerHandle(timer);
}
/**
* {@inheritDoc}
*/
@Override
public void createJob(String name,
Date time,
Serializable context,
Serializable user,
boolean stopOnError,
boolean stopOnConcurrent,
boolean expire,
Collection callbacks) {
createScheduleJob(name, time, context, user, stopOnError, stopOnConcurrent, expire, callbacks);
}
/**
* {@inheritDoc}
*/
@Override
public void createJob(String name, long time, boolean stopOnError, RUNNABLE... callbacks) {
createScheduleJob(name, time, stopOnError, callbacks);
}
/**
* {@inheritDoc}
*/
@Override
public void createJob(String name,
ScheduleExpression scheduleExpression,
boolean stopOnError,
RUNNABLE... callbacks) {
createScheduleJob(name, scheduleExpression, stopOnError, callbacks);
}
/**
* initializeJob
*
* @param name unique job name
* @param callbacks all runners to be started on timer expiration
* @param context the jobs context (used by the callback)
* @param user user that starts the job
* @param stopOnError whether to stop the job on any error
* @param stopOnConcurrent whether to avoid running more than one job parallel.
* @return
* @throws IllegalStateException
* @throws NoSuchObjectLocalException
* @throws EJBException
* @throws NoMoreTimeoutsException
*/
protected Job initializeJob(String name,
Collection callbacks,
Serializable context,
Serializable user,
boolean stopOnError,
boolean stopOnConcurrent) throws IllegalStateException,
NoSuchObjectLocalException,
EJBException,
NoMoreTimeoutsException {
if (callbacks == null || callbacks.size() == 0) {
throw new ManagedException("list of callbacks is null or empty - cannot start the runJobs service");
}
//timer.getHandle() is only callable on persisted timers!
Job job = new Job(name, null, callbacks, context, user, stopOnError, stopOnConcurrent);
jobs.add(job);
return job;
}
/**
* getJob
*
* @param the the jobs name or unique name (see {@link Job#getName()} and {@link Job#getUniqueName()}. if you give
* only the short name of {@link Job#getName()} the first found item will be returned.
* @return job or null
*/
@Override
public Job getJob(String name) {
for (Job job : jobs) {
if (job.getUniqueName().equals(name) || job.getName().equals(name)) {
return job;
}
}
return null;
}
/**
* @deprecated: use {@link #getJob(String)} instead getJob
* @param th the jobs timerhandle
* @return job or null
*/
public Job getJob(TimerHandle th) {
for (Job job : jobs) {
if (job.getTimerHandle().equals(th)) {
return job;
}
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public void disposeScheduleJob(TimerHandle timerHandle) {
Job job = getJob(timerHandle);
if (job != null) {
LOG.info("disposing job " + job);
timerHandle.getTimer().cancel();
jobs.remove(job);
} else {
LOG.warn("timerHandle " + timerHandle + " not available!");
}
}
/**
* {@inheritDoc}
*/
@Override
public void disposeScheduleJob(String name) {
Job job = getJob(name);
if (job != null) {
LOG.info("disposing job " + job);
job.getTimerHandle().getTimer().cancel();
jobs.remove(job);
} else {
LOG.warn("job with name " + name + " not available!");
}
}
/**
* {@inheritDoc}
*/
@Override
public Collection> getCurrentJobs() {
checkExpiredJobs();
return jobs;
}
/**
* removes expired jobs
*/
private void checkExpiredJobs() {
Collection> expired = new LinkedList>();
for (Job> job : jobs) {
if (job.isExpired()) {
expired.add(job);
}
}
if (expired.size() > 0) {
LOG.info("removing expired jobs: " + StringUtil.toString(expired, 200));
jobs.removeAll(expired);
}
}
/**
* {@inheritDoc}
*/
@Override
public Collection getCurrentJobNames() {
//because the timerHandle inside a job is not transferable, only the names will be evaluated.
Collection jobNames = new ArrayList(jobs.size());
for (Job> job : jobs) {
jobNames.add(job.getName());
}
return jobNames;
}
/**
* {@inheritDoc}
*/
@Override
public void disposeAllScheduledJobs() {
// final Collection timers = timerService.getTimers();
for (final Job job : jobs) {
job.getTimerHandle().getTimer().cancel();
}
jobs.clear();
}
/**
* will be started, if the timer, created by {@link #createScheduleJob(ScheduleExpression, Collection, boolean)} has
* a timeout. the callback-methods will be run synchron, but in an own thread, to avoid service-blocking
*
* @param timer
*/
@Timeout
protected void runJob(final Timer timer) {
final Job job = getJob(timer.getHandle());
if (job.isStopOnConcurrent()) {
Job> runningJob = getRunningJob();
if (runningJob != null) {
LOG.error("job-start canceled. no concurrent running jobs are allowed.\nPlease use argument 'stopOnConcurrent=false' if you want to allow concurrent running jobs!");
RuntimeException ex = new ManagedException("tsl2nano.concurrentfailure", new Object[] { job,
runningJob });
job.setLastException(ex);
throw ex;
}
}
/*
* don't block the service.
*/
final AbstractJobScheduleServiceBean _this = this;
new Thread(new Runnable() {
@Override
public void run() {
job.setAsStarted();
for (final RUNNABLE runnable : job.getCallbacks()) {
try {
_this.run(runnable, job.getContext());
} catch (final Throwable ex) {
//if the jvm crashes, the job will never be stopped!
if (job.isStopOnError()) {
RuntimeException fwdEx = ManagedException.toRuntimeEx(ex, true, true);
stopRun(timer, job, fwdEx);
throw fwdEx;
} else {
job.setLastException(ex);
LOG.error("continuing after error:" + ex.toString(), ex);
}
}
}
stopRun(timer, job, null);
}
}).start();
}
/**
* will be called, to set new properties on the stopped job. this has to be done asynchrony to let the timerservice
* refresh the timer instance after leaving the timeout method {@link #runJob(Timer)}.
*
* @param timer timer of stopped job
* @param job job to refresh informations on
* @param ex (optional) error instance
*/
protected void stopRun(final Timer timer, final Job job, final Exception ex) {
Runnable stopJob = new Runnable() {
@Override
public void run() {
//be sure, the timeout method 'runJob' was ended
try {
Thread.sleep(ex != null ? 500 : 100);
} catch (InterruptedException e) {
LOG.warn("sleep before stoping job was interrupted: " + e);
} finally {
job.setAsStopped(ex);
addToHistory(job);
//if job is expired, removed it from list
if (getNextTimeout(timer) == null) {
jobs.remove(job);
}
LOG.info("next run will be: " + getNextTimeout(timer));
}
}
};
new Thread(stopJob, job.toString()).start();
}
protected Date getNextTimeout(Timer timer) {
try {
return timer.getNextTimeout();
} catch (Exception e) {
LOG.warn(e.toString());
return null;
}
}
protected void addToHistory(Job job) {
jobHistory.add(new JobHistoryEntry(job));
//backup to file-system (should be done through file-connector)
FileUtil.save(FILE_HISTORY, (Serializable)jobHistory);
}
/**
* {@inheritDoc}
*/
@Override
public Collection getJobHistory() {
return jobHistory;
}
/**
* hasRunningJob
*
* @return true, if another job is running.
*/
protected Job getRunningJob() {
for (Job job : jobs) {
if (job.isRunning()) {
return job;
}
}
return null;
}
/**
* unused yet...
* @param time time for next timeout
* @param interval period before and after 'time' to be valid for job
* @return first job that has a next timeout at time +- interval
*/
public Job getJobAt(Date time, long interval) {
for (Job job : getCurrentJobs()) {
Date nextTimeout = job.getTimerHandle().getTimer().getNextTimeout();
long differenceInMS = time.getTime() - nextTimeout.getTime();
// time between jobs is less than configured interval
if (differenceInMS > -interval && differenceInMS < interval) {
LOG.debug("found job for time" + time + ": " + job);
return job;
}
}
return null;
}
/**
* runCallback
*
* @param runnable to start
*/
protected abstract void run(RUNNABLE runnable, Serializable context);
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy