/* ================================================================== * JobSupport.java - Jun 30, 2011 5:09:59 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; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.osgi.service.event.Event; import org.osgi.service.event.EventAdmin; /** * Base helper class for a scheduled job. * * <p> * The configurable properties of this class are: * </p> * * <dl class="class-properties"> * <dt>maximumWaitMs</dt> * <dd>The maximum time, in milliseconds, to allow for the job to execute before * it is considered a failed job. Defaults to <b>15 minutes</b>.</dd> * * <dt>jobId</dt> * <dd>The unique ID of the job to schedule.</dd> * * <dt>jobTopic</dt> * <dd>The {@link Event} topic to use for this job.</dd> * * <dt>jobGroup</dt> * <dd>The job group to use. Defaults to <b>Datum</b>.</dd> * * <dt>jobCron</dt> * <dd>The job cron expression to use for scheduling this job. Defaults to * <code>0 0/1 * * * ?</code> (once per minute)</dd>. * </dl> * * @author matt * @version 1.3 */ public abstract class JobSupport extends EventHandlerSupport { private final EventAdmin eventAdmin; private long maximumWaitMs = 15L * 60L * 1000L; private String jobId; private String jobTopic; private String jobGroup; public String jobCron = "0 0/1 * * * ?"; private ExecutorService executorService = Executors.newCachedThreadPool(); /** * Constructor. * * @param eventAdmin */ public JobSupport(EventAdmin eventAdmin) { super(); this.eventAdmin = eventAdmin; } @Override protected final void handleEventInternal(final Event event) throws Exception { if ( event.getTopic().equals(SchedulerConstants.TOPIC_SCHEDULER_READY) ) { schedulerReady(event); return; } if ( jobId != null && !jobId.equals(event.getProperty(SchedulerConstants.JOB_ID)) ) { // same topic, wrong job return; } // kick off to another thread, so we don't block the event handler thread (and possibly get blacklisted) executorService.submit(new Runnable() { @Override public void run() { Event ack = null; Throwable thrown = null; boolean complete = false; try { complete = handleJob(event); } catch ( Exception e ) { log.warn("Exception in job {}", event.getTopic(), e); thrown = e; } finally { ack = handleJobCompleteEvent(event, complete, thrown); if ( ack != null ) { eventAdmin.postEvent(ack); } } } }); } /** * Handle the completion of a job. * * This method is called internally by {@link #handleEventInternal(Event)} * after {@link #handleJob(Event)} returns. Extending classes may want to * customize the resulting job acknowledgement event. * * @param jobEvent * The original job event that initiated the job. * @param complete * The result of {@link #handleJob(Event)}, or <em>false</em> if that * method throws an exception. * @param thrown * An exception thrown by {@link #handleJob(Event)}, or <em>null</em> * if none thrown. * @return A new job acknowledgement event. * @see SchedulerUtils#createJobCompleteEvent(Event) * @see SchedulerUtils#createJobFailureEvent(Event, Throwable) * @since 1.3 */ protected Event handleJobCompleteEvent(Event jobEvent, boolean complete, Throwable thrown) { if ( complete ) { return SchedulerUtils.createJobCompleteEvent(jobEvent); } return SchedulerUtils.createJobFailureEvent(jobEvent, thrown); } /** * Handle the "scheduler ready" event, to give class a chance to perform * startup tasks. This implementation generates a {@code TOPIC_JOB_REQUEST} * event using the configured job properties and cron schedule on this * class. * * @param event * the event * @throws Exception * if any error occurs */ protected void schedulerReady(Event event) throws Exception { Map<String, Object> props = new HashMap<String, Object>(5); props.put(SchedulerConstants.JOB_ID, jobId); props.put(SchedulerConstants.JOB_CRON_EXPRESSION, jobCron); props.put(SchedulerConstants.JOB_GROUP, jobGroup); props.put(SchedulerConstants.JOB_MAX_WAIT, maximumWaitMs); props.put(SchedulerConstants.JOB_TOPIC, jobTopic); Event e = new Event(SchedulerConstants.TOPIC_JOB_REQUEST, props); getEventAdmin().postEvent(e); } /** * Handle the job. * * @param job * the job details * @return <em>true</em> if job completed successfully, <em>false</em> * otherwise * @throws Exception * if any error occurs */ protected abstract boolean handleJob(Event job) throws Exception; /** * Get the EventAdmin. * * @return the EventAdmin */ protected EventAdmin getEventAdmin() { return eventAdmin; } public long getMaximumWaitMs() { return maximumWaitMs; } public String getJobId() { return jobId; } public String getJobTopic() { return jobTopic; } public String getJobGroup() { return jobGroup; } public String getJobCron() { return jobCron; } public void setJobId(String jobId) { this.jobId = jobId; } public void setJobTopic(String jobTopic) { this.jobTopic = jobTopic; } public void setMaximumWaitMs(long maximumWaitMs) { this.maximumWaitMs = maximumWaitMs; } public void setJobCron(String jobCron) { this.jobCron = jobCron; } public void setJobGroup(String jobGroup) { this.jobGroup = jobGroup; } public ExecutorService getExecutorService() { return executorService; } public void setExecutorService(ExecutorService executorService) { this.executorService = executorService; } }