/* ==================================================================
* SchedulerManager.java - Jun 29, 2011 4:22:24 PM
*
* Copyright 2007-2011 SolarNetwork.net Dev Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
* ==================================================================
*/
package net.solarnetwork.central.scheduler.internal;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.osgi.service.event.EventHandler;
import org.quartz.JobExecutionContext;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import net.solarnetwork.central.domain.PingTest;
import net.solarnetwork.central.domain.PingTestResult;
import net.solarnetwork.central.scheduler.EventHandlerSupport;
import net.solarnetwork.central.scheduler.SchedulerConstants;
import net.solarnetwork.central.scheduler.SchedulerUtils;
/**
* Manage the lifecycle of the Quartz Scheduler.
*
* @author matt
* @version 1.2
*/
public class SchedulerManager extends EventHandlerSupport
implements ApplicationListener<ContextRefreshedEvent>, EventHandler, PingTest {
private static final String TEST_TOPIC = "net/solarnetwork/central/scheduler/TEST";
private final Scheduler scheduler;
private final EventAdmin eventAdmin;
private long blockedJobMaxSeconds = 300;
/**
* Constructor.
*
* @param scheduler
* the Scheduler
*/
public SchedulerManager(Scheduler scheduler, EventAdmin eventAdmin) {
this.scheduler = scheduler;
this.eventAdmin = eventAdmin;
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
try {
for ( String triggerGroup : scheduler.getTriggerGroupNames() ) {
for ( TriggerKey triggerKey : scheduler
.getTriggerKeys(GroupMatcher.triggerGroupEquals(triggerGroup)) ) {
Trigger t = scheduler.getTrigger(triggerKey);
log.debug("Found trigger: " + t);
}
}
// fire off test Trigger to verify scheduler operational
Map<String, Object> props = new HashMap<String, Object>(5);
props.put(SchedulerConstants.JOB_ID, "SchedulerManager.Startup");
props.put(SchedulerConstants.JOB_DATE, new Date().getTime() + 5000);
props.put(SchedulerConstants.JOB_GROUP, "Test");
props.put(SchedulerConstants.JOB_TOPIC, TEST_TOPIC);
Event e = new Event(SchedulerConstants.TOPIC_JOB_REQUEST, props);
eventAdmin.postEvent(e);
} catch ( SchedulerException e ) {
log.error("Exception finding triggers", e);
}
}
@Override
protected void handleEventInternal(Event event) throws Exception {
log.debug("Got event: {}", event.getTopic());
if ( SchedulerConstants.TOPIC_JOB_COMPLETE.equals(event.getTopic()) ) {
NotificationJob job = getRunningJob(event);
if ( job != null ) {
job.finish(event);
}
} else if ( SchedulerConstants.TOPIC_JOB_FAILURE.equals(event.getTopic()) ) {
NotificationJob job = getRunningJob(event);
if ( job != null ) {
job.finish(event);
}
} else if ( TEST_TOPIC.equals(event.getTopic()) ) {
Event ack = SchedulerUtils.createJobCompleteEvent(event);
eventAdmin.postEvent(ack);
// post "we're ready" event
Map<String, Object> props = new HashMap<String, Object>(1);
Event e = new Event(SchedulerConstants.TOPIC_SCHEDULER_READY, props);
eventAdmin.postEvent(e);
}
}
private NotificationJob getRunningJob(Event event) throws SchedulerException {
final String jobId = (String) event.getProperty(SchedulerConstants.JOB_ID);
if ( jobId == null ) {
log.debug("Can't find running job for event because JOB_ID missing");
return null;
}
final String jobGroup = (String) event.getProperty(SchedulerConstants.JOB_GROUP);
for ( JobExecutionContext jec : scheduler.getCurrentlyExecutingJobs() ) {
Trigger t = jec.getTrigger();
if ( jobId.equals(t.getKey().getName())
&& (jobGroup == null || jobGroup.equals(t.getKey().getGroup())) ) {
return (NotificationJob) jec.getJobInstance();
}
}
log.warn("Running job {} in group {} not found", jobId, jobGroup);
return null;
}
// PingTest support
@Override
public String getPingTestId() {
return getClass().getName();
}
@Override
public String getPingTestName() {
return "Job Scheduler";
}
@Override
public long getPingTestMaximumExecutionMilliseconds() {
return 2000;
}
@Override
public PingTestResult performPingTest() throws Exception {
Scheduler s = scheduler;
if ( s.isInStandbyMode() ) {
return new PingTestResult(false, "Scheduler is in standby mode");
}
if ( s.isShutdown() ) {
return new PingTestResult(false, "Scheduler is shut down");
}
int triggerCount = 0;
long now = System.currentTimeMillis();
final String stateErrorTemplate = "Trigger %s.%s is in the %s state, since %tc";
for ( String triggerGroup : s.getTriggerGroupNames() ) {
for ( TriggerKey triggerKey : scheduler
.getTriggerKeys(GroupMatcher.triggerGroupEquals(triggerGroup)) ) {
triggerCount += 1;
Trigger.TriggerState triggerState = s.getTriggerState(triggerKey);
Date lastFireTime = null;
Trigger trigger = null;
switch (triggerState) {
case BLOCKED:
trigger = s.getTrigger(triggerKey);
lastFireTime = trigger.getPreviousFireTime();
if ( lastFireTime != null
&& lastFireTime.getTime() + (blockedJobMaxSeconds * 1000) < now ) {
return new PingTestResult(false, String.format(stateErrorTemplate,
triggerGroup, triggerKey.getName(), "BLOCKED", lastFireTime));
}
case ERROR:
trigger = s.getTrigger(triggerKey);
lastFireTime = trigger.getPreviousFireTime();
return new PingTestResult(false, String.format(stateErrorTemplate, triggerGroup,
triggerKey.getName(), "ERROR", lastFireTime));
default:
// no error
}
}
}
String msg = String.format("Scheduler is running as expected; %d triggers configured.",
triggerCount);
return new PingTestResult(true, msg);
}
public long getBlockedJobMaxSeconds() {
return blockedJobMaxSeconds;
}
/**
* A minimum amount of seconds before a blocked job results in an error.
*
* @param blockedJobMaxSeconds
* The number of seconds.
*/
public void setBlockedJobMaxSeconds(long blockedJobMaxSeconds) {
this.blockedJobMaxSeconds = blockedJobMaxSeconds;
}
}