com.jaspersoft.jasperserver.api.engine.scheduling.quartz.ReportJobsQuartzScheduler.java Source code

Java tutorial

Introduction

Here is the source code for com.jaspersoft.jasperserver.api.engine.scheduling.quartz.ReportJobsQuartzScheduler.java

Source

/*
 * Copyright (C) 2005 - 2014 TIBCO Software Inc. All rights reserved.
 * http://www.jaspersoft.com.
 *
 * Unless you have purchased  a commercial license agreement from Jaspersoft,
 * the following license terms  apply:
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License  as
 * published by the Free Software Foundation, either version 3 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 Affero  General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public  License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package com.jaspersoft.jasperserver.api.engine.scheduling.quartz;

import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.CronScheduleBuilder.cronSchedule;
import static org.quartz.TriggerBuilder.newTrigger;

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.Calendar;

import com.jaspersoft.jasperserver.api.engine.scheduling.domain.ReportJob;
import com.jaspersoft.jasperserver.api.engine.scheduling.domain.ReportJobCalendarTrigger;
import com.jaspersoft.jasperserver.api.engine.scheduling.domain.ReportJobIdHolder;
import com.jaspersoft.jasperserver.api.engine.scheduling.domain.ReportJobRuntimeInformation;
import com.jaspersoft.jasperserver.api.engine.scheduling.domain.ReportJobSimpleTrigger;
import com.jaspersoft.jasperserver.api.engine.scheduling.domain.ReportJobTrigger;
import com.jaspersoft.jasperserver.api.engine.scheduling.service.ReportJobsPersistenceService;

import com.jaspersoft.jasperserver.api.metadata.common.domain.InputControl;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.impl.triggers.AbstractTrigger;
import org.quartz.impl.matchers.KeyMatcher;
import org.springframework.beans.factory.InitializingBean;

import com.jaspersoft.jasperserver.api.JSException;
import com.jaspersoft.jasperserver.api.JSExceptionWrapper;
import com.jaspersoft.jasperserver.api.common.domain.ExecutionContext;
import com.jaspersoft.jasperserver.api.common.domain.ValidationErrors;
import com.jaspersoft.jasperserver.api.common.domain.impl.ValidationErrorImpl;
import com.jaspersoft.jasperserver.api.engine.scheduling.service.ReportJobsScheduler;
import com.jaspersoft.jasperserver.api.engine.scheduling.service.ReportSchedulerListener;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.jaspersoft.jasperserver.api.logging.diagnostic.domain.DiagnosticAttribute;
import com.jaspersoft.jasperserver.api.logging.diagnostic.helper.DiagnosticAttributeBuilder;
import com.jaspersoft.jasperserver.api.logging.diagnostic.service.Diagnostic;
import com.jaspersoft.jasperserver.api.logging.diagnostic.service.DiagnosticCallback;

/**
 * Implementation of {@link ReportJobsScheduler).
 *
 * @author Lucian Chirita (lucianc@users.sourceforge.net)
 * @version $Id: ReportJobsQuartzScheduler.java 47331 2014-07-18 09:13:06Z kklein $
 */
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public class ReportJobsQuartzScheduler implements ReportJobsScheduler, InitializingBean, Diagnostic {

    protected static final Log log = LogFactory.getLog(ReportJobsQuartzScheduler.class);

    private static final String GROUP = "ReportJobs";
    private static final String TRIGGER_LISTENER_NAME = "reportSchedulerTriggerListener";

    private static final long COEFFICIENT_MINUTE = 60l * 1000l;
    private static final long COEFFICIENT_HOUR = 60l * COEFFICIENT_MINUTE;
    private static final long COEFFICIENT_DAY = 24l * COEFFICIENT_HOUR;
    private static final long COEFFICIENT_WEEK = 7l * COEFFICIENT_DAY;

    private static final int COUNT_WEEKDAYS = 7;
    private static final int COUNT_MONTHS = 12;

    private Scheduler scheduler;
    private ReportJobsPersistenceService persistenceService;
    private Class reportExecutionJobClass;

    private final Set listeners;
    private final SchedulerListener schedulerListener;
    private final TriggerListener triggerListener;

    private static final String SMART_POLICY = "SMART_POLICY";
    private static final String MISFIRE_INSTRUCTION_FIRE_NOW = "MISFIRE_INSTRUCTION_FIRE_NOW";
    private static final String MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = "MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY";
    private static final String MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT = "MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT";
    private static final String MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT = "MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT";
    private static final String MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT = "MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT";
    private static final String MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT = "MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT";
    private static final String MISFIRE_INSTRUCTION_DO_NOTHING = "MISFIRE_INSTRUCTION_DO_NOTHING";
    private static final String MISFIRE_INSTRUCTION_FIRE_ONCE_NOW = "MISFIRE_INSTRUCTION_FIRE_ONCE_NOW";

    private String repeatingSimpleJobMisfirePolicy;
    private String singleSimpleJobMisfirePolicy;
    private String calendarJobMisfirePolicy;

    public ReportJobsQuartzScheduler() {

        listeners = new HashSet();

        schedulerListener = new ReportSchedulerQuartzListener();
        triggerListener = new ReportSchedulerTriggerListener(TRIGGER_LISTENER_NAME);
    }

    public String getSingleSimpleJobMisfirePolicy() {
        return singleSimpleJobMisfirePolicy;
    }

    public void setSingleSimpleJobMisfirePolicy(String singleSimpleJobMisfirePolicy) {
        this.singleSimpleJobMisfirePolicy = singleSimpleJobMisfirePolicy;
    }

    public String getRepeatingSimpleJobMisfirePolicy() {
        return repeatingSimpleJobMisfirePolicy;
    }

    public void setRepeatingSimpleJobMisfirePolicy(String repeatingSimpleJobMisfirePolicy) {
        this.repeatingSimpleJobMisfirePolicy = repeatingSimpleJobMisfirePolicy;
    }

    public String getcalendarJobMisfirePolicy() {
        return calendarJobMisfirePolicy;
    }

    public void setcalendarJobMisfirePolicy(String calendarJobMisfirePolicy) {
        this.calendarJobMisfirePolicy = calendarJobMisfirePolicy;
    }

    public Scheduler getScheduler() {
        return scheduler;
    }

    public void setScheduler(Scheduler scheduler) {
        this.scheduler = scheduler;
    }

    public ReportJobsPersistenceService getPersistenceService() {
        return persistenceService;
    }

    public void setPersistenceService(ReportJobsPersistenceService persistenceService) {
        this.persistenceService = persistenceService;
    }

    public Class getReportExecutionJobClass() {
        return reportExecutionJobClass;
    }

    public void setReportExecutionJobClass(Class reportExecutionJobClass) {
        this.reportExecutionJobClass = reportExecutionJobClass;
    }

    public void afterPropertiesSet() {
        try {
            getScheduler().getListenerManager().addTriggerListener(triggerListener,
                    (List<Matcher<TriggerKey>>) null);
            getScheduler().getListenerManager().addSchedulerListener(schedulerListener);

        } catch (SchedulerException e) {
            log.error("Error (de)registering Quartz listener", e);
            throw new JSExceptionWrapper(e);
        }
    }

    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public void scheduleJob(ExecutionContext context, ReportJob job) {
        JobDetail jobDetail = createJobDetail(job);
        Trigger trigger = createTrigger(job);
        try {
            scheduler.scheduleJob(jobDetail, trigger);
            if (log.isDebugEnabled()) {
                log.debug("Created job " + jobDetail.getKey().getName() + " and trigger "
                        + trigger.getKey().getName() + " for job " + job.getId());
                //log.debug("Created job " + jobDetail.getFullName() + " and trigger " + trigger.getFullName() + " for job " + job.getId());
            }
        } catch (SchedulerException e) {
            log.error("Error scheduling Quartz job", e);
            throw new JSExceptionWrapper(e);
        }
    }

    protected JobDetail createJobDetail(ReportJob job) {
        String jobName = jobName(job.getId());
        JobDetail jobDetail = newJob(getReportExecutionJobClass()).withIdentity(jobName, GROUP)
                .requestRecovery(true).build();
        return jobDetail;
    }

    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public void rescheduleJob(ExecutionContext context, ReportJob job) {
        try {
            Trigger oldTrigger = getReportJobTrigger(job.getId());

            String jobName = jobName(job.getId());
            Trigger trigger = createTrigger(job);

            if (oldTrigger == null) {
                JobDetail jobDetail = createJobDetail(job);
                scheduler.scheduleJob(jobDetail, trigger);
                if (log.isDebugEnabled()) {
                    log.debug("Scheduled trigger " + trigger.getKey().getName() + " for job " + job.getId());
                    //log.debug("Scheduled trigger " + trigger.getFullName() + " for job " + job.getId());
                }
            } else {
                scheduler.rescheduleJob(oldTrigger.getKey(), trigger);
                //scheduler.rescheduleJob(oldTrigger.getName(), oldTrigger.getGroup(), trigger);

                if (log.isDebugEnabled()) {
                    log.debug("Trigger " + oldTrigger.getKey().getName() + " rescheduled by "
                            + trigger.getKey().getName() + " for job " + job.getId());
                    //log.debug("Trigger " + oldTrigger.getFullName() + " rescheduled by " + trigger.getFullName() + " for job " + job.getId());
                }
            }
        } catch (SchedulerException e) {
            log.error("Error rescheduling Quartz job", e);
            throw new JSExceptionWrapper(e);
        }
    }

    protected Trigger getReportJobTrigger(long jobId) throws SchedulerException {
        Trigger trigger;
        String jobName = jobName(jobId);
        List<? extends Trigger> triggers = scheduler.getTriggersOfJob(getJobKey(jobName));

        // Filtering triggers by group to exclude system triggers.
        List<Trigger> filteredTriggersList = new ArrayList<Trigger>();
        for (Trigger currTrigger : triggers) {
            if (GROUP.equals(currTrigger.getKey().getGroup())) {
                filteredTriggersList.add(currTrigger);
            }
        }
        triggers = filteredTriggersList;
        if (triggers == null || triggers.isEmpty()) {
            trigger = null;

            if (log.isDebugEnabled()) {
                log.debug("No trigger found for job " + jobId);
            }
        } else if (triggers.size() == 1) {
            trigger = triggers.get(0);
            if (log.isDebugEnabled()) {
                log.debug("Trigger " + trigger.getKey().getName() + " found for job " + jobId);
                //log.debug("Trigger " + trigger.getFullName() + " found for job " + jobId);
            }
        } else {
            throw new JSException("jsexception.job.has.more.than.one.trigger", new Object[] { new Long(jobId) });
        }
        return trigger;
    }

    protected String jobName(long jobId) {
        return "job_" + jobId;
    }

    protected String triggerName(ReportJobTrigger jobTrigger) {
        return "trigger_" + jobTrigger.getId() + "_" + jobTrigger.getVersion();
    }

    protected Trigger createTrigger(ReportJob reportJob) {
        Trigger trigger;
        ReportJobTrigger jobTrigger = reportJob.getTrigger();
        if (jobTrigger instanceof ReportJobSimpleTrigger) {
            trigger = createTrigger((ReportJobSimpleTrigger) jobTrigger);
        } else if (jobTrigger instanceof ReportJobCalendarTrigger) {
            trigger = createTrigger((ReportJobCalendarTrigger) jobTrigger);
        } else {
            String quotedJobTrigger = "\"" + jobTrigger.getClass().getName() + "\"";
            throw new JSException("jsexception.job.unknown.trigger.type", new Object[] { quotedJobTrigger });
        }

        JobDataMap jobDataMap = trigger.getJobDataMap();
        jobDataMap.put(
                com.jaspersoft.jasperserver.api.engine.scheduling.quartz.ReportExecutionJob.JOB_DATA_KEY_DETAILS_ID,
                new Long(reportJob.getId()));
        jobDataMap.put(
                com.jaspersoft.jasperserver.api.engine.scheduling.quartz.ReportExecutionJob.JOB_DATA_KEY_USERNAME,
                reportJob.getUsername());

        TriggerKey tk = getTriggerKey(jobTrigger);
        Matcher<TriggerKey> matcher = KeyMatcher.keyEquals(tk);
        try {
            getScheduler().getListenerManager().addTriggerListener(triggerListener, matcher);
        } catch (org.quartz.SchedulerException e) {
            throw new JSException("Error adding Quartz Trigger Listener: " + e.getMessage());
        }
        return trigger;
    }

    protected Trigger createTrigger(ReportJobSimpleTrigger jobTrigger) {
        String triggerName = triggerName(jobTrigger);
        Date startDate = getStartDate(jobTrigger);
        Date endDate = getEndDate(jobTrigger);
        String calendarName = jobTrigger.getCalendarName();

        int repeatCount = repeatCount(jobTrigger);
        Trigger trigger = null;
        String policy = "";

        long interval = 0;
        if (repeatCount != 0) {
            int recurrenceInterval = jobTrigger.getRecurrenceInterval().intValue();
            long unitCoefficient = getIntervalUnitCoefficient(jobTrigger);
            interval = recurrenceInterval * unitCoefficient;
            policy = repeatingSimpleJobMisfirePolicy;
        } else {
            policy = singleSimpleJobMisfirePolicy;
            interval = 0;
        }

        if (policy.equals(MISFIRE_INSTRUCTION_FIRE_NOW)) {
            trigger = newTrigger().withIdentity(triggerName, GROUP).startAt(startDate)
                    .modifiedByCalendar(calendarName)
                    .withSchedule(simpleSchedule().withIntervalInMilliseconds(interval).withRepeatCount(repeatCount)
                            .withMisfireHandlingInstructionFireNow())
                    .build();

        } else if (policy.equals(MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY)) {
            trigger = newTrigger().withIdentity(triggerName, GROUP).startAt(startDate)
                    .modifiedByCalendar(calendarName)
                    .withSchedule(simpleSchedule().withIntervalInMilliseconds(interval).withRepeatCount(repeatCount)
                            .withMisfireHandlingInstructionIgnoreMisfires())
                    .build();
        } else if (policy.equals(MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT)) {
            trigger = newTrigger().withIdentity(triggerName, GROUP).startAt(startDate)
                    .modifiedByCalendar(calendarName)
                    .withSchedule(simpleSchedule().withIntervalInMilliseconds(interval).withRepeatCount(repeatCount)
                            .withMisfireHandlingInstructionNextWithExistingCount())
                    .build();
        } else if (policy.equals(MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT)) {
            trigger = newTrigger().withIdentity(triggerName, GROUP).startAt(startDate)
                    .modifiedByCalendar(calendarName)
                    .withSchedule(simpleSchedule().withIntervalInMilliseconds(interval).withRepeatCount(repeatCount)
                            .withMisfireHandlingInstructionNextWithRemainingCount())
                    .build();
        }

        else if (policy.equals(MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT)) {
            trigger = newTrigger().withIdentity(triggerName, GROUP).startAt(startDate)
                    .modifiedByCalendar(calendarName)
                    .withSchedule(simpleSchedule().withIntervalInMilliseconds(interval).withRepeatCount(repeatCount)
                            .withMisfireHandlingInstructionNowWithExistingCount())
                    .build();
        }

        else if (policy.equals(MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT)) {
            trigger = newTrigger().withIdentity(triggerName, GROUP).startAt(startDate)
                    .modifiedByCalendar(calendarName)
                    .withSchedule(simpleSchedule().withIntervalInMilliseconds(interval).withRepeatCount(repeatCount)
                            .withMisfireHandlingInstructionNowWithRemainingCount())
                    .build();
        } else {
            trigger = newTrigger().withIdentity(triggerName, GROUP).startAt(startDate).endAt(endDate)
                    .modifiedByCalendar(calendarName)
                    .withSchedule(
                            simpleSchedule().withIntervalInMilliseconds(interval).withRepeatCount(repeatCount))
                    .build();
        }

        return trigger;
    }

    protected Trigger createTrigger(ReportJobCalendarTrigger jobTrigger) {
        String triggerName = triggerName(jobTrigger);
        Date startDate = getStartDate(jobTrigger);
        Date endDate = getEndDate(jobTrigger);
        String calendarName = jobTrigger.getCalendarName();
        String cronExpression = getCronExpression(jobTrigger);
        Trigger trigger = null;
        try {
            // NOTE: We are NOT going to specify a MisfireHandlingInstruction - this allows the Quartz Scheduler
            // to use its default MISFIRE_INSTRUCTION_SMART_POLICY behavior - and for a calendar scheduled job,
            // the Quartz Scheduler will automatically reschedule this job to be run one time when a downed server
            // is restarted (and then continue with the calendar job schedule)...
            if (calendarJobMisfirePolicy.equals(MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY)) {

                trigger = newTrigger().withIdentity(triggerName, GROUP).startAt(startDate).endAt(endDate)
                        .modifiedByCalendar(calendarName)
                        .withSchedule(cronSchedule(cronExpression).inTimeZone(getTriggerTimeZone(jobTrigger))
                                .withMisfireHandlingInstructionIgnoreMisfires())
                        .build();

            } else if (calendarJobMisfirePolicy.equals(MISFIRE_INSTRUCTION_DO_NOTHING)) {
                trigger = newTrigger().withIdentity(triggerName, GROUP).startAt(startDate).endAt(endDate)
                        .modifiedByCalendar(calendarName)
                        .withSchedule(cronSchedule(cronExpression).inTimeZone(getTriggerTimeZone(jobTrigger))
                                .withMisfireHandlingInstructionDoNothing())
                        .build();
            } else if (calendarJobMisfirePolicy.equals(MISFIRE_INSTRUCTION_FIRE_ONCE_NOW)) {
                trigger = newTrigger().withIdentity(triggerName, GROUP).startAt(startDate).endAt(endDate)
                        .modifiedByCalendar(calendarName)
                        .withSchedule(cronSchedule(cronExpression).inTimeZone(getTriggerTimeZone(jobTrigger))
                                .withMisfireHandlingInstructionFireAndProceed())
                        .build();

            } else {
                trigger = newTrigger().withIdentity(triggerName, GROUP).startAt(startDate).endAt(endDate)
                        .modifiedByCalendar(calendarName)
                        .withSchedule(cronSchedule(cronExpression).inTimeZone(getTriggerTimeZone(jobTrigger)))
                        .build();

            }

            return trigger;
        } catch (Exception e) {
            log.error("Error creating Quartz Cron trigger", e);
            throw new JSExceptionWrapper(e);
        }
    }

    protected long getIntervalUnitCoefficient(ReportJobSimpleTrigger jobTrigger) {
        long coefficient;
        switch (jobTrigger.getRecurrenceIntervalUnit().byteValue()) {
        case ReportJobSimpleTrigger.INTERVAL_MINUTE:
            coefficient = COEFFICIENT_MINUTE;
            break;
        case ReportJobSimpleTrigger.INTERVAL_HOUR:
            coefficient = COEFFICIENT_HOUR;
            break;
        case ReportJobSimpleTrigger.INTERVAL_DAY:
            coefficient = COEFFICIENT_DAY;
            break;
        case ReportJobSimpleTrigger.INTERVAL_WEEK:
            coefficient = COEFFICIENT_WEEK;
            break;
        default:
            throw new JSException("jsexception.job.unknown.interval.unit",
                    new Object[] { jobTrigger.getRecurrenceIntervalUnit() });
        }
        return coefficient;
    }

    protected Date getEndDate(ReportJobTrigger jobTrigger) {
        return translateFromTriggerTimeZone(jobTrigger, jobTrigger.getEndDate());
    }

    protected Date translateFromTriggerTimeZone(ReportJobTrigger jobTrigger, Date date) {
        if (date != null) {
            TimeZone tz = getTriggerTimeZone(jobTrigger);
            if (tz != null) {
                date = DateBuilder.translateTime(date, TimeZone.getDefault(), tz);
                //date = TriggerUtils.translateTime(date, TimeZone.getDefault(), tz);
            }
        }
        return date;
    }

    protected Date getStartDate(ReportJobTrigger jobTrigger) {
        Date startDate;
        switch (jobTrigger.getStartType()) {
        case ReportJobTrigger.START_TYPE_NOW:
            Calendar calendar = Calendar.getInstance();
            calendar.set(Calendar.SECOND, 0);
            calendar.set(Calendar.MILLISECOND, 0);
            startDate = calendar.getTime();
            break;
        case ReportJobTrigger.START_TYPE_SCHEDULE:
            startDate = translateFromTriggerTimeZone(jobTrigger, jobTrigger.getStartDate());
            break;
        default:
            throw new JSException("jsexception.job.unknown.start.type",
                    new Object[] { new Byte(jobTrigger.getStartType()) });
        }
        return startDate;
    }

    protected int repeatCount(ReportJobSimpleTrigger jobTrigger) {
        int recurrenceCount = jobTrigger.getOccurrenceCount();
        int repeatCount;
        switch (recurrenceCount) {
        case ReportJobSimpleTrigger.RECUR_INDEFINITELY:
            repeatCount = SimpleTrigger.REPEAT_INDEFINITELY;
            break;
        default:
            repeatCount = recurrenceCount - 1;
            break;
        }
        return repeatCount;
    }

    protected TimeZone getTriggerTimeZone(ReportJobTrigger jobTrigger) {
        String tzId = jobTrigger.getTimezone();
        TimeZone tz;
        if (tzId == null || tzId.length() == 0) {
            tz = null;
        } else {
            tz = TimeZone.getTimeZone(tzId);
            if (tz == null) {
                String quotedTzId = "\"" + tzId + "\"";
                throw new JSException("jsexception.unknown.timezone", new Object[] { quotedTzId });
            }
        }
        return tz;
    }

    protected String getCronExpression(ReportJobCalendarTrigger jobTrigger) {
        String minutes = jobTrigger.getMinutes();
        String hours = jobTrigger.getHours();
        String weekDays;
        String monthDays;
        switch (jobTrigger.getDaysType()) {
        case ReportJobCalendarTrigger.DAYS_TYPE_ALL:
            weekDays = "?";
            monthDays = "*";
            break;
        case ReportJobCalendarTrigger.DAYS_TYPE_WEEK:
            weekDays = enumerateCronVals(jobTrigger.getWeekDays(), COUNT_WEEKDAYS);
            monthDays = "?";
            break;
        case ReportJobCalendarTrigger.DAYS_TYPE_MONTH:
            weekDays = "?";
            monthDays = jobTrigger.getMonthDays();
            break;
        default:
            throw new JSException("jsexception.job.unknown.calendar.trigger.days.type",
                    new Object[] { new Byte(jobTrigger.getDaysType()) });
        }
        String months = enumerateCronVals(jobTrigger.getMonths(), COUNT_MONTHS);

        StringBuffer cronExpression = new StringBuffer();
        cronExpression.append("0 ");
        cronExpression.append(minutes);
        cronExpression.append(' ');
        cronExpression.append(hours);
        cronExpression.append(' ');
        cronExpression.append(monthDays);
        cronExpression.append(' ');
        cronExpression.append(months);
        cronExpression.append(' ');
        cronExpression.append(weekDays);

        return cronExpression.toString();
    }

    protected int getMisfireCode(ReportJobTrigger jobTrigger) {
        int i = jobTrigger.getMisfireInstruction();

        //
        // 2012-03-09       thorick
        //
        // JS_MISFIRE_INSTRUCTION_NOT_SET is a special JS only code that means that
        //   the misfire instruction has NOT been set.
        // this unfortunate construct is required because the value of Quartz MISFIRE_INSTRUCTION_SMART_POLICY == 0
        // conflicts with the values will be placed into an upgraded JS ReportJobTrigger table which are also == 0.
        //
        //  sorry.
        //
        if (i == jobTrigger.JS_MISFIRE_INSTRUCTION_NOT_SET)
            return SimpleTrigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY;
        if (jobTrigger instanceof ReportJobSimpleTrigger) {
            if (i == ReportJobSimpleTrigger.JS_MISFIRE_INSTRUCTION_FIRE_NOW)
                return SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW;
            if (i == ReportJobSimpleTrigger.JS_MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT)
                return SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT;
            if (i == ReportJobSimpleTrigger.JS_MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT)
                return SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT;
            if (i == ReportJobSimpleTrigger.JS_MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT)
                return SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT;
            if (i == ReportJobSimpleTrigger.JS_MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT)
                return SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT;
        } else if (jobTrigger instanceof ReportJobCalendarTrigger) {
            if (i == ReportJobCalendarTrigger.JS_MISFIRE_INSTRUCTION_DO_NOTHING)
                return (CalendarIntervalTrigger.MISFIRE_INSTRUCTION_DO_NOTHING);
            if (i == ReportJobCalendarTrigger.JS_MISFIRE_INSTRUCTION_FIRE_ONCE_NOW)
                return (CalendarIntervalTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW);
        }

        if (i == jobTrigger.JS_MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY)
            return SimpleTrigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY;
        if (i == jobTrigger.JS_MISFIRE_INSTRUCTION_SMART_POLICY)
            return SimpleTrigger.MISFIRE_INSTRUCTION_SMART_POLICY;
        throw new JSException("Unhandled misfire instruction of value " + i);
    }

    protected String enumerateCronVals(SortedSet vals, int totalCount) {
        if (vals == null || vals.isEmpty()) {
            throw new JSException("jsexception.no.values.to.enumerate");
        }

        if (vals.size() == totalCount) {
            return "*";
        }

        StringBuffer enumStr = new StringBuffer();
        for (Iterator it = vals.iterator(); it.hasNext();) {
            Byte val = (Byte) it.next();
            enumStr.append(val.byteValue());
            enumStr.append(',');
        }
        return enumStr.substring(0, enumStr.length() - 1);
    }

    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public void removeScheduledJob(ExecutionContext context, long jobId) {
        try {
            String jobName = jobName(jobId);
            if (scheduler.deleteJob(getJobKey(jobName))) {
                if (log.isDebugEnabled()) {
                    log.debug("Job " + jobName + "deleted");
                }
            } else {
                log.info("Quartz job " + jobId + " was not found to be deleted");
            }
        } catch (SchedulerException e) {
            log.error("Error deleting Quartz job " + jobId, e);
            throw new JSExceptionWrapper(e);
        }
    }

    public ReportJobRuntimeInformation[] getJobsRuntimeInformation(ExecutionContext context, long[] jobIds) {
        if (jobIds == null) {
            return null;
        }

        try {
            Set executingJobNames = getExecutingJobNames();
            ReportJobRuntimeInformation[] infos = new ReportJobRuntimeInformation[jobIds.length];
            for (int i = 0; i < jobIds.length; i++) {
                infos[i] = getJobRuntimeInformation(jobIds[i], executingJobNames);
            }
            return infos;
        } catch (SchedulerException e) {
            log.error("Error while fetching Quartz runtime information", e);
            throw new JSExceptionWrapper(e);
        }
    }

    private Trigger reInitTrigger(long jobId) {
        try {
            ReportJob reportJob = persistenceService.loadJob(null, new ReportJobIdHolder(jobId));
            rescheduleJob(null, reportJob);
            Trigger trigger = getReportJobTrigger(jobId);
            return trigger;
        } catch (Exception ex) {
            log.error("Fail to re-init quartz trigger", ex);
        }
        return null;
    }

    protected ReportJobRuntimeInformation getJobRuntimeInformation(long jobId, Set executingJobNames)
            throws SchedulerException {
        ReportJobRuntimeInformation info = new ReportJobRuntimeInformation();
        Trigger trigger = getReportJobTrigger(jobId);
        if (trigger == null)
            trigger = reInitTrigger(jobId);
        if (trigger == null) {
            info.setState(ReportJobRuntimeInformation.STATE_UNKNOWN);
        } else {
            info.setPreviousFireTime(trigger.getPreviousFireTime());
            if (trigger.mayFireAgain()) {
                info.setNextFireTime(trigger.getNextFireTime());
            }

            byte state = getJobState(trigger, executingJobNames);
            info.setState(state);
        }
        return info;
    }

    protected byte getJobState(Trigger trigger, Set executingJobNames) throws SchedulerException {
        byte state;
        Trigger.TriggerState quartzState = scheduler.getTriggerState(trigger.getKey());

        switch (quartzState) {
        case NORMAL:
        case BLOCKED:
            state = executingJobNames.contains(trigger.getJobKey().getName())
                    ? ReportJobRuntimeInformation.STATE_EXECUTING
                    : ReportJobRuntimeInformation.STATE_NORMAL;
            break;
        case COMPLETE:
            state = executingJobNames.contains(trigger.getJobKey().getName())
                    ? ReportJobRuntimeInformation.STATE_EXECUTING
                    : ReportJobRuntimeInformation.STATE_COMPLETE;
            break;
        case PAUSED:
            state = ReportJobRuntimeInformation.STATE_PAUSED;
            break;
        case ERROR:
            state = ReportJobRuntimeInformation.STATE_ERROR;
            break;
        default:
            state = ReportJobRuntimeInformation.STATE_UNKNOWN;
            break;
        }
        return state;
    }

    protected Set getExecutingJobNames() throws SchedulerException {
        List executingJobs = scheduler.getCurrentlyExecutingJobs();
        Set executingJobNames = new HashSet();
        for (Iterator iter = executingJobs.iterator(); iter.hasNext();) {
            JobExecutionContext executionContext = (JobExecutionContext) iter.next();
            JobDetail jobDetail = executionContext.getJobDetail();
            if (jobDetail.getKey().getGroup().equals(GROUP)) {
                executingJobNames.add(jobDetail.getKey().getName());
            }
        }
        return executingJobNames;
    }

    protected List<JobDetail> getAllJobsOfScheduler() throws SchedulerException {
        final List<JobDetail> result = new ArrayList<JobDetail>();
        for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(GROUP))) {
            final JobDetail jobDetail;
            try {
                jobDetail = scheduler.getJobDetail(jobKey);
                if (jobDetail != null) {
                    result.add(jobDetail);
                }
            } catch (final Exception e) {
            }
        }
        return result;
    }

    @Override
    public Map<DiagnosticAttribute, DiagnosticCallback> getDiagnosticData() {
        return new DiagnosticAttributeBuilder()
                .addDiagnosticAttribute(DiagnosticAttributeBuilder.TOTAL_SCHEDULED_JOBS,
                        new DiagnosticCallback<Integer>() {
                            @Override
                            public Integer getDiagnosticAttributeValue() {
                                Integer totalScheduledJobs = 0;
                                try {
                                    totalScheduledJobs = getAllJobsOfScheduler().size();
                                } catch (SchedulerException e) {
                                    // Empty Body
                                }
                                return totalScheduledJobs;
                            }
                        })
                .addDiagnosticAttribute(DiagnosticAttributeBuilder.TOTAL_RUNNING_JOBS,
                        new DiagnosticCallback<Integer>() {
                            @Override
                            public Integer getDiagnosticAttributeValue() {
                                Integer totalRunningJobs = 0;
                                try {
                                    totalRunningJobs = getExecutingJobNames().size();
                                } catch (SchedulerException e) {
                                    // Empty Body
                                }
                                return totalRunningJobs;
                            }
                        })
                .addDiagnosticAttribute(DiagnosticAttributeBuilder.RUNNING_JOBS_LIST,
                        new DiagnosticCallback<Map<String, Map<String, Long>>>() {
                            @Override
                            public Map<String, Map<String, Long>> getDiagnosticAttributeValue() {
                                Map<String, Map<String, Long>> runningJobList = new HashMap<String, Map<String, Long>>();
                                try {
                                    List<JobExecutionContext> jobExecutionContexts = scheduler
                                            .getCurrentlyExecutingJobs();
                                    if (jobExecutionContexts != null && jobExecutionContexts.size() > 0) {
                                        for (JobExecutionContext jobExecutionContext : jobExecutionContexts) {
                                            JobDetail jobDetail = jobExecutionContext.getJobDetail();
                                            if (jobDetail.getKey().getGroup().equals(GROUP)) {
                                                Map<String, Long> reportUrisWithTimeExecutions = new HashMap<String, Long>();
                                                Long executionTime = System.currentTimeMillis()
                                                        - jobExecutionContext.getFireTime().getTime();
                                                reportUrisWithTimeExecutions.put(
                                                        ((ReportExecutionJob) (jobExecutionContext)
                                                                .getJobInstance()).reportUnit.getURI(),
                                                        executionTime / 1000);
                                                runningJobList.put(
                                                        jobExecutionContext.getJobDetail().getKey().getName(),
                                                        reportUrisWithTimeExecutions);
                                            }
                                        }
                                    }
                                } catch (SchedulerException e) {
                                    // Empty Body
                                }
                                return runningJobList;
                            }
                        })
                .build();
    }

    public void addReportSchedulerListener(ReportSchedulerListener listener) {
        synchronized (listeners) {
            listeners.add(listener);
        }
    }

    public synchronized void removeReportSchedulerListener(ReportSchedulerListener listener) {
        synchronized (listeners) {
            listeners.remove(listener);
        }
    }

    public void pause(List<ReportJob> jobs, boolean all) {
        if (all) {
            pauseById((List<ReportJobIdHolder>) null, all);
            return;
        }
        List<ReportJobIdHolder> ids = new ArrayList<ReportJobIdHolder>();
        for (ReportJob job : jobs) {
            ids.add(new ReportJobIdHolder(job.getId()));
        }
        pauseById(ids, all);
        return;
    }

    public void pauseById(List<ReportJobIdHolder> jobIds, boolean all) {
        boolean exception = false;
        StringBuilder sb = new StringBuilder();
        if (all) {
            try {
                //p("pause ALL");
                scheduler.pauseAll();
                //p("pause complete");
                return;
            } catch (SchedulerException e) {
                sb.append("Exception while attempting to do 'pauseAll()' on Quartz Scheduler. ");
                throw new JSException(sb.toString(), e.getUnderlyingException());
            }
        }
        if (jobIds == null || jobIds.size() <= 0) {
            return;
        }
        for (ReportJobIdHolder id : jobIds) {
            String jobName = jobName(id.getId());
            try {
                //p("pause job "+jobName);
                scheduler.pauseJob(getJobKey(jobName));
                sb.append("pause of ReportJob id='" + id + "' successful.");
            } catch (SchedulerException e) {
                exception = true;
                sb.append("Exception while attempting to do pause of ReportJob id='" + id
                        + "' on Quartz Scheduler: ");
                sb.append(e.getMessage());
                sb.append("\n");
            }
        }
        //p("pause complete");
        if (exception) {
            //p("pause encountered problems "+sb.toString());
            throw new JSException(sb.toString());
        }
    }

    public void resume(List<ReportJob> jobs, boolean all) {
        if (all) {
            resumeById((List<ReportJobIdHolder>) null, all);
            return;
        }
        List<ReportJobIdHolder> ids = new ArrayList<ReportJobIdHolder>();
        for (ReportJob job : jobs) {
            ids.add(new ReportJobIdHolder(job.getId()));
        }
        resumeById(ids, all);
        return;
    }

    public void resumeById(List<ReportJobIdHolder> jobIds, boolean all) {
        boolean exception = false;
        StringBuilder sb = new StringBuilder();
        if (all) {
            try {
                //p("resume ALL");
                scheduler.resumeAll();
                return;
            } catch (SchedulerException e) {
                sb.append("Exception while attempting to do 'resumeAll()' on Quartz Scheduler. ");
                throw new JSException(e.getUnderlyingException());
            }
        }
        if (jobIds == null || jobIds.size() <= 0) {
            return;
        }
        for (ReportJobIdHolder id : jobIds) {
            String jobName = jobName(id.getId());
            try {
                //p("resume job "+jobName);
                scheduler.resumeJob(getJobKey(jobName));
                sb.append("resume of ReportJob id='" + id.getId() + "' successful.");
            } catch (SchedulerException e) {
                exception = true;
                sb.append("Exception while attempting to do resume of ReportJob id='" + id.getId()
                        + "' on Quartz Scheduler: ");
                sb.append(e.getMessage());
                sb.append("\n");
            }
        }
        if (exception) {
            throw new JSException(sb.toString());
        }
    }

    protected void notifyListenersOfFinalizedJob(long jobId) {
        synchronized (listeners) {
            for (Iterator it = listeners.iterator(); it.hasNext();) {
                ReportSchedulerListener listener = (ReportSchedulerListener) it.next();
                listener.reportJobFinalized(jobId);
            }
        }
    }

    protected void reportTriggerFinalized(Trigger trigger) {
        long jobId = trigger.getJobDataMap().getLongValue(
                com.jaspersoft.jasperserver.api.engine.scheduling.quartz.ReportExecutionJob.JOB_DATA_KEY_DETAILS_ID);
        //long jobId = trigger.getJobDataMap().getLong(ReportExecutionJob.JOB_DATA_KEY_DETAILS_ID);
        notifyListenersOfFinalizedJob(jobId);
    }

    // convenience method to get a Quartz JobKey from JasperServer Object
    protected JobKey getJobKey(ReportJob job) {
        return getJobKey(jobName(job.getId()));
    }

    protected JobKey getJobKey(String jobName) {
        return new JobKey(jobName, GROUP);
    }

    protected TriggerKey getTriggerKey(ReportJobTrigger jobTrigger) {
        return new TriggerKey(triggerName(jobTrigger), GROUP);
    }

    public void addCalendar(String calName, org.quartz.Calendar calendar, boolean replace, boolean updateTriggers)
            throws JSException {
        try {
            scheduler.addCalendar(calName, calendar, replace, updateTriggers);
        } catch (Exception ex) {
            throw new JSException(ex);
        }
    }

    public boolean deleteCalendar(java.lang.String calName) throws JSException {
        try {
            return scheduler.deleteCalendar(calName);
        } catch (Exception ex) {
            throw new JSException(ex);
        }
    }

    public org.quartz.Calendar getCalendar(java.lang.String calName) throws JSException {
        try {
            return scheduler.getCalendar(calName);
        } catch (Exception ex) {
            throw new JSException(ex);
        }
    }

    public List<String> getCalendarNames() throws JSException {
        try {
            return scheduler.getCalendarNames();
        } catch (Exception ex) {
            throw new JSException(ex);
        }
    }

    protected class ReportSchedulerQuartzListener implements SchedulerListener {

        public ReportSchedulerQuartzListener() {
        }

        /**
         * <p>
         * Called by the <code>{@link Scheduler}</code> when a <code>{@link org.quartz.JobDetail}</code>
         * is unscheduled.
         * </p>
         *
         * @see SchedulerListener#schedulingDataCleared()
         */
        public void jobUnscheduled(TriggerKey triggerKey) {
            if (log.isDebugEnabled()) {
                log.debug("Quartz job with triggerKey: " + triggerKey + " unscheduled");
            }
        }

        /**
         * <p>
         * Called by the <code>{@link Scheduler}</code> when a <code>{@link Trigger}</code>
         * has been paused.
         * </p>
         */
        public void triggerPaused(TriggerKey triggerKey) {
            if (log.isDebugEnabled()) {
                log.debug("Quartz job trigger" + triggerKey + " paused");
            }
        }

        /**
         * <p>
         * Called by the <code>{@link Scheduler}</code> when a
         * group of <code>{@link Trigger}s</code> has been paused.
         * </p>
         * <p/>
         * <p>If all groups were paused then triggerGroup will be null</p>
         *
         * @param triggerGroup the paused group, or null if all were paused
         */
        public void triggersPaused(String triggerGroup) {
            if (log.isDebugEnabled()) {
                log.debug("Quartz job trigger group " + triggerGroup + " paused ");
            }
        }

        /**
         * <p>
         * Called by the <code>{@link Scheduler}</code> when a <code>{@link Trigger}</code>
         * has been un-paused.
         * </p>
         */
        public void triggerResumed(TriggerKey triggerKey) {
            if (log.isDebugEnabled()) {
                log.debug("Quartz job trigger " + triggerKey + " resumed ");
            }
        }

        /**
         * <p>
         * Called by the <code>{@link Scheduler}</code> when a
         * group of <code>{@link Trigger}s</code> has been un-paused.
         * </p>
         */
        public void triggersResumed(String triggerGroup) {
            if (log.isDebugEnabled()) {
                log.debug("Quartz job trigger group" + triggerGroup + " resumed ");
            }
        }

        /**
         * <p>
         * Called by the <code>{@link Scheduler}</code> when a <code>{@link org.quartz.JobDetail}</code>
         * has been added.
         * </p>
         */
        public void jobAdded(JobDetail jobDetail) {
            if (log.isDebugEnabled()) {
                log.debug("Quartz job " + jobDetail.getKey() + " added. ");
            }
        }

        /**
         * <p>
         * Called by the <code>{@link Scheduler}</code> when a <code>{@link org.quartz.JobDetail}</code>
         * has been deleted.
         * </p>
         */
        public void jobDeleted(JobKey jobKey) {
            if (log.isDebugEnabled()) {
                log.debug("Quartz job " + jobKey + " deleted ");
            }
        }

        /**
         * <p>
         * Called by the <code>{@link Scheduler}</code> when a <code>{@link org.quartz.JobDetail}</code>
         * has been paused.
         * </p>
         */
        public void jobPaused(JobKey jobKey) {
            if (log.isDebugEnabled()) {
                log.debug("Quartz job " + jobKey + " paused ");
            }
        }

        /**
         * <p>
         * Called by the <code>{@link Scheduler}</code> when a
         * group of <code>{@link org.quartz.JobDetail}s</code> has been paused.
         * </p>
         *
         * @param jobGroup the paused group, or null if all were paused
         */
        public void jobsPaused(String jobGroup) {
            if (log.isDebugEnabled()) {
                log.debug("Quartz job Group " + jobGroup + " paused ");
            }
        }

        /**
         * <p>
         * Called by the <code>{@link Scheduler}</code> when a <code>{@link org.quartz.JobDetail}</code>
         * has been un-paused.
         * </p>
         */
        public void jobResumed(JobKey jobKey) {
            if (log.isDebugEnabled()) {
                log.debug("Quartz job " + jobKey + " resumed  ");
            }
        }

        /**
         * <p>
         * Called by the <code>{@link Scheduler}</code> when a
         * group of <code>{@link org.quartz.JobDetail}s</code> has been un-paused.
         * </p>
         */
        public void jobsResumed(String jobGroup) {
            if (log.isDebugEnabled()) {
                log.debug("Quartz job  Group " + jobGroup + " resumed  ");
            }
        }

        /**
         * <p>
         * Called by the <code>{@link Scheduler}</code> to inform the listener
         * that it has move to standby mode.
         * </p>
         */
        public void schedulerInStandbyMode() {
            if (log.isDebugEnabled()) {
                log.debug("Quartz Scheduler in standby mode ");
            }
        }

        /**
         * <p>
         * Called by the <code>{@link Scheduler}</code> to inform the listener
         * that it has started.
         * </p>
         */
        public void schedulerStarted() {
            if (log.isDebugEnabled()) {
                log.debug("Quartz Scheduler started");
            }
        }

        /**
         * <p>
         * Called by the <code>{@link Scheduler}</code> to inform the listener
         * that it has begun the shutdown sequence.
         * </p>
         */
        public void schedulerShuttingdown() {
            if (log.isDebugEnabled()) {
                log.debug("Quartz Scheduler shutting down");
            }
        }

        /**
         * Called by the <code>{@link Scheduler}</code> to inform the listener
         * that all jobs, triggers and calendars were deleted.
         */
        public void schedulingDataCleared() {
            if (log.isDebugEnabled()) {
                log.debug("Quartz Scheduler Data Cleared");
            }
        }

        public void jobScheduled(Trigger trigger) {
            if (log.isDebugEnabled()) {
                log.debug("Quartz job " + trigger.getKey() + " scheduled by trigger " + trigger.getKey());
            }
        }

        public void jobUnscheduled(String name, String group) {
            if (log.isDebugEnabled()) {
                log.debug("Quartz job unscheduled " + group + "." + name);
            }
        }

        public void triggerFinalized(Trigger trigger) {
            if (log.isDebugEnabled()) {
                log.debug("Quartz trigger finalized " + trigger.getKey());
            }

            if (trigger.getKey().getGroup().equals(GROUP)) {
                reportTriggerFinalized(trigger);
            }
        }

        public void schedulerError(String msg, SchedulerException cause) {
            if (log.isInfoEnabled()) {
                log.info("Quartz scheduler error: " + msg, cause);
            }
        }

        public void schedulerShutdown() {
            if (log.isInfoEnabled()) {
                log.info("Quartz scheduler shutdown");
            }
        }

    }

    protected class ReportSchedulerTriggerListener implements TriggerListener {

        private final String name;

        public ReportSchedulerTriggerListener(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void triggerFired(Trigger trigger, JobExecutionContext context) {
            if (log.isDebugEnabled()) {
                log.debug("Quartz trigger fired " + trigger.getKey());
            }
        }

        public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
            return false;
        }

        public void triggerMisfired(Trigger trigger) {
            if (log.isDebugEnabled()) {
                log.debug("Quartz trigger misfired " + trigger.getKey());
            }

            if (trigger.getKey().getGroup().equals(GROUP) && trigger.getFireTimeAfter(new Date()) == null) {
                // TODO SteveRosen - we may not need this logic here now that Quartz is handling all misfirings...
                // reportTriggerFinalized(trigger);
            }
        }

        public void triggerComplete(Trigger trigger, JobExecutionContext context,
                Trigger.CompletedExecutionInstruction triggerInstructionCode) {
            if (log.isDebugEnabled()) {
                log.debug("Quartz trigger complete " + trigger.getKey() + " triggerInstructionCode="
                        + triggerInstructionCode);
            }
        }

    }

    public void validate(ReportJob job, ValidationErrors errors) {
        Trigger quartzTrigger = createTrigger(job);

        // this method is intended for use by the Quartz Scheduler and is not meant for public use
        //   but that's OK, we're just testing the veracity of the Trigger
        AbstractTrigger abstrTrigger = (AbstractTrigger) quartzTrigger;
        Date firstFireTime = abstrTrigger.computeFirstFireTime(null);
        if (firstFireTime == null) {
            errors.add(new ValidationErrorImpl("error.report.job.trigger.no.fire", null, null, "trigger"));
        }
    }

    private void p(String s) {
        System.err.println(this.getClass().getName() + " - " + s);
    }

}