/// <summary> /// Add the given <see cref="IJob" /> to the Scheduler - with no associated /// <see cref="Trigger" />. The <see cref="IJob" /> will be 'dormant' until /// it is scheduled with a <see cref="Trigger" />, or <see cref="IScheduler.TriggerJob(string ,string)" /> /// is called for it. /// <p> /// The <see cref="IJob" /> must by definition be 'durable', if it is not, /// SchedulerException will be thrown. /// </p> /// </summary> public virtual void AddJob(SchedulingContext ctxt, JobDetail jobDetail, bool replace) { ValidateState(); if (!jobDetail.Durable && !replace) { throw new SchedulerException("Jobs added with no trigger must be durable.", SchedulerException.ErrorClientError); } resources.JobStore.StoreJob(ctxt, jobDetail, replace); }
/// <summary> /// Create a JobExcecutionContext with the given context data. /// </summary> public JobExecutionContext(IScheduler scheduler, TriggerFiredBundle firedBundle, IJob job) { this.scheduler = scheduler; trigger = firedBundle.Trigger; calendar = firedBundle.Calendar; jobDetail = firedBundle.JobDetail; this.job = job; recovering = firedBundle.Recovering; fireTimeUtc = firedBundle.FireTimeUtc; scheduledFireTimeUtc = firedBundle.ScheduledFireTimeUtc; prevFireTimeUtc = firedBundle.PrevFireTimeUtc; nextFireTimeUtc = firedBundle.NextFireTimeUtc; jobDataMap = new JobDataMap(); jobDataMap.PutAll(jobDetail.JobDataMap); jobDataMap.PutAll(trigger.JobDataMap); }
/// <summary> /// Notifies the job store job complete. /// </summary> /// <param name="ctxt">The job scheduling context.</param> /// <param name="trigger">The trigger.</param> /// <param name="detail">The detail.</param> /// <param name="instCode">The instruction code.</param> protected internal virtual void NotifyJobStoreJobComplete(SchedulingContext ctxt, Trigger trigger, JobDetail detail, SchedulerInstruction instCode) { resources.JobStore.TriggeredJobComplete(ctxt, trigger, detail, instCode); }
/// <summary> /// Add the <see cref="IJob" /> identified by the given /// <see cref="JobDetail" /> to the Scheduler, and /// associate the given <see cref="Trigger" /> with it. /// <p> /// If the given Trigger does not reference any <see cref="IJob" />, then it /// will be set to reference the Job passed with it into this method. /// </p> /// </summary> public virtual DateTime ScheduleJob(SchedulingContext ctxt, JobDetail jobDetail, Trigger trigger) { ValidateState(); if (jobDetail == null) { throw new SchedulerException("JobDetail cannot be null", SchedulerException.ErrorClientError); } if (trigger == null) { throw new SchedulerException("Trigger cannot be null", SchedulerException.ErrorClientError); } jobDetail.Validate(); if (trigger.JobName == null) { trigger.JobName = jobDetail.Name; trigger.JobGroup = jobDetail.Group; } else if (trigger.JobName != null && !trigger.JobName.Equals(jobDetail.Name)) { throw new SchedulerException("Trigger does not reference given job!", SchedulerException.ErrorClientError); } else if (trigger.JobGroup != null && !trigger.JobGroup.Equals(jobDetail.Group)) { throw new SchedulerException("Trigger does not reference given job!", SchedulerException.ErrorClientError); } trigger.Validate(); ICalendar cal = null; if (trigger.CalendarName != null) { cal = resources.JobStore.RetrieveCalendar(ctxt, trigger.CalendarName); if (cal == null) { throw new SchedulerException(string.Format(CultureInfo.InvariantCulture, "Calendar not found: {0}", trigger.CalendarName), SchedulerException.ErrorPersistenceCalendarDoesNotExist); } } NullableDateTime ft = trigger.ComputeFirstFireTimeUtc(cal); if (!ft.HasValue) { throw new SchedulerException("Based on configured schedule, the given trigger will never fire.", SchedulerException.ErrorClientError); } resources.JobStore.StoreJobAndTrigger(ctxt, jobDetail, trigger); NotifySchedulerThread(trigger.GetNextFireTimeUtc()); NotifySchedulerListenersScheduled(trigger); return ft.Value; }
/// <summary> /// Select the JobDetail object for a given job name / group name. /// </summary> /// <param name="conn">The DB Connection.</param> /// <param name="jobName">The job name whose listeners are wanted.</param> /// <param name="groupName">The group containing the job.</param> /// <param name="loadHelper">The load helper.</param> /// <returns>The populated JobDetail object.</returns> public virtual JobDetail SelectJobDetail(ConnectionAndTransactionHolder conn, string jobName, string groupName, ITypeLoadHelper loadHelper) { using (IDbCommand cmd = PrepareCommand(conn, ReplaceTablePrefix(SqlSelectJobDetail))) { AddCommandParameter(cmd, 1, "jobName", jobName); AddCommandParameter(cmd, 2, "jobGroup", groupName); using (IDataReader rs = cmd.ExecuteReader()) { JobDetail job = null; if (rs.Read()) { job = new JobDetail(); job.Name = GetString(rs[ColumnJobName]); job.Group = GetString(rs[ColumnJobGroup]); job.Description = GetString(rs[ColumnDescription]); job.JobType = loadHelper.LoadType(GetString(rs[ColumnJobClass])); job.Durable = GetBoolean(rs[ColumnIsDurable]); job.Volatile = GetBoolean(rs[ColumnIsVolatile]); job.RequestsRecovery = GetBoolean(rs[ColumnRequestsRecovery]); IDictionary map; if (CanUseProperties) { map = GetMapFromProperties(rs, 8); } else { map = (IDictionary)GetObjectFromBlob(rs, 8); } if (null != map) { job.JobDataMap = new JobDataMap(map); } } return job; } } }
//--------------------------------------------------------------------------- // triggers //--------------------------------------------------------------------------- /// <summary> /// Insert the base trigger data. /// </summary> /// <param name="conn">the DB Connection</param> /// <param name="trigger">the trigger to insert</param> /// <param name="state">the state that the trigger should be stored in</param> /// <param name="jobDetail">The job detail.</param> /// <returns>the number of rows inserted</returns> public virtual int InsertTrigger(ConnectionAndTransactionHolder conn, Trigger trigger, string state, JobDetail jobDetail) { byte[] baos = null; if (trigger.JobDataMap.Count > 0) { baos = SerializeJobData(trigger.JobDataMap); } using (IDbCommand cmd = PrepareCommand(conn, ReplaceTablePrefix(SqlInsertTrigger))) { AddCommandParameter(cmd, 1, "triggerName", trigger.Name); AddCommandParameter(cmd, 2, "triggerGroup", trigger.Group); AddCommandParameter(cmd, 3, "triggerJobName", trigger.JobName); AddCommandParameter(cmd, 4, "triggerJobGroup", trigger.JobGroup); AddCommandParameter(cmd, 5, "triggerVolatile", GetDbBooleanValue(trigger.Volatile)); AddCommandParameter(cmd, 6, "triggerDescription", trigger.Description); if (trigger.GetNextFireTimeUtc().HasValue) { AddCommandParameter(cmd, 7, "triggerNextFireTime", Convert.ToDecimal(trigger.GetNextFireTimeUtc().Value.Ticks)); } else { AddCommandParameter(cmd, 7, "triggerNextFireTime", null); } long prevFireTime = -1; if (trigger.GetPreviousFireTimeUtc().HasValue) { prevFireTime = trigger.GetPreviousFireTimeUtc().Value.Ticks; } AddCommandParameter(cmd, 8, "triggerPreviousFireTime", Convert.ToDecimal(prevFireTime)); AddCommandParameter(cmd, 9, "triggerState", state); string paramName = "triggerType"; if (trigger is SimpleTrigger && !trigger.HasAdditionalProperties) { AddCommandParameter(cmd, 10, paramName, TriggerTypeSimple); } else if (trigger is CronTrigger && !trigger.HasAdditionalProperties) { AddCommandParameter(cmd, 10, paramName, TriggerTypeCron); } else { // (trigger instanceof BlobTrigger or additional properties in sub-class AddCommandParameter(cmd, 10, paramName, TriggerTypeBlob); } AddCommandParameter(cmd, 11, "triggerStartTime", Convert.ToDecimal(trigger.StartTimeUtc.Ticks)); long endTime = 0; if (trigger.EndTimeUtc.HasValue) { endTime = trigger.EndTimeUtc.Value.Ticks; } AddCommandParameter(cmd, 12, "triggerEndTime", Convert.ToDecimal(endTime)); AddCommandParameter(cmd, 13, "triggerCalendarName", trigger.CalendarName); AddCommandParameter(cmd, 14, "triggerMisfireInstruction", trigger.MisfireInstruction); paramName = "triggerJobJobDataMap"; if (baos != null) { AddCommandParameter(cmd, 15, paramName, baos, dbProvider.Metadata.DbBinaryType); } else { AddCommandParameter(cmd, 15, paramName, null, dbProvider.Metadata.DbBinaryType); } AddCommandParameter(cmd, 16, "triggerPriority", trigger.Priority); int insertResult = cmd.ExecuteNonQuery(); if (insertResult > 0) { string[] trigListeners = trigger.TriggerListenerNames; for (int i = 0; trigListeners != null && i < trigListeners.Length; i++) { InsertTriggerListener(conn, trigger, trigListeners[i]); } } return insertResult; } }
/// <summary> /// Update the job data map for the given job. /// </summary> /// <param name="conn">The conn.</param> /// <param name="job">the job to update</param> /// <returns>the number of rows updated</returns> public virtual int UpdateJobData(ConnectionAndTransactionHolder conn, JobDetail job) { byte[] baos = SerializeJobData(job.JobDataMap); using (IDbCommand cmd = PrepareCommand(conn, ReplaceTablePrefix(SqlUpdateJobData))) { AddCommandParameter(cmd, 1, "jobDataMap", baos, dbProvider.Metadata.DbBinaryType); AddCommandParameter(cmd, 2, "jobName", job.Name); AddCommandParameter(cmd, 3, "jobGroup", job.Group); return cmd.ExecuteNonQuery(); } }
/// <summary> /// Associate a listener with a job. /// </summary> /// <param name="conn">The DB Connection.</param> /// <param name="job">The job to associate with the listener.</param> /// <param name="listener">The listener to insert.</param> /// <returns>The number of rows inserted.</returns> public virtual int InsertJobListener(ConnectionAndTransactionHolder conn, JobDetail job, string listener) { using (IDbCommand cmd = PrepareCommand(conn, ReplaceTablePrefix(SqlInsertJobListener))) { AddCommandParameter(cmd, 1, "jobName", job.Name); AddCommandParameter(cmd, 2, "jobGroup", job.Group); AddCommandParameter(cmd, 3, "listener", listener); return cmd.ExecuteNonQuery(); } }
/// <summary> /// Insert a fired trigger. /// </summary> /// <param name="conn">the DB Connection</param> /// <param name="trigger">the trigger</param> /// <param name="state">the state that the trigger should be stored in</param> /// <param name="job">The job.</param> /// <returns>the number of rows inserted</returns> public virtual int InsertFiredTrigger(ConnectionAndTransactionHolder conn, Trigger trigger, string state, JobDetail job) { using (IDbCommand cmd = PrepareCommand(conn, ReplaceTablePrefix(SqlInsertFiredTrigger))) { AddCommandParameter(cmd, 1, "triggerEntryId", trigger.FireInstanceId); AddCommandParameter(cmd, 2, "triggerName", trigger.Name); AddCommandParameter(cmd, 3, "triggerGroup", trigger.Group); AddCommandParameter(cmd, 4, "triggerVolatile", GetDbBooleanValue(trigger.Volatile)); AddCommandParameter(cmd, 5, "triggerInstanceName", instanceId); AddCommandParameter(cmd, 6, "triggerFireTime", Convert.ToDecimal(trigger.GetNextFireTimeUtc().Value.Ticks)); AddCommandParameter(cmd, 7, "triggerState", state); if (job != null) { AddCommandParameter(cmd, 8, "triggerJobName", trigger.JobName); AddCommandParameter(cmd, 9, "triggerJobGroup", trigger.JobGroup); AddCommandParameter(cmd, 10, "triggerJobStateful", GetDbBooleanValue(job.Stateful)); AddCommandParameter(cmd, 11, "triggerJobRequestsRecovery", GetDbBooleanValue(job.RequestsRecovery)); } else { AddCommandParameter(cmd, 8, "triggerJobName", null); AddCommandParameter(cmd, 9, "triggerJobGroup", null); AddCommandParameter(cmd, 10, "triggerJobStateful", GetDbBooleanValue(false)); AddCommandParameter(cmd, 11, "triggerJobRequestsRecovery", GetDbBooleanValue(false)); } AddCommandParameter(cmd, 12, "triggerPriority", trigger.Priority); return cmd.ExecuteNonQuery(); } }
/// <summary> /// Update the job detail record. /// </summary> /// <param name="conn">The DB Connection.</param> /// <param name="job">The job to update.</param> /// <returns>Number of rows updated.</returns> public virtual int UpdateJobDetail(ConnectionAndTransactionHolder conn, JobDetail job) { byte[] baos = SerializeJobData(job.JobDataMap); using (IDbCommand cmd = PrepareCommand(conn, ReplaceTablePrefix(SqlUpdateJobDetail))) { AddCommandParameter(cmd, 1, "jobDescription", job.Description); AddCommandParameter(cmd, 2, "jobType", GetStorableJobTypeName(job.JobType)); AddCommandParameter(cmd, 3, "jobDurable", GetDbBooleanValue(job.Durable)); AddCommandParameter(cmd, 4, "jobVolatile", GetDbBooleanValue(job.Volatile)); AddCommandParameter(cmd, 5, "jobStateful", GetDbBooleanValue(job.Stateful)); AddCommandParameter(cmd, 6, "jobRequestsRecovery", GetDbBooleanValue(job.RequestsRecovery)); AddCommandParameter(cmd, 7, "jobDataMap", baos, dbProvider.Metadata.DbBinaryType); AddCommandParameter(cmd, 8, "jobName", job.Name); AddCommandParameter(cmd, 9, "jobGroup", job.Group); int insertResult = cmd.ExecuteNonQuery(); if (insertResult > 0) { DeleteJobListeners(conn, job.Name, job.Group); String[] jobListeners = job.JobListenerNames; for (int i = 0; jobListeners != null && i < jobListeners.Length; i++) { InsertJobListener(conn, job, jobListeners[i]); } } return insertResult; } }
/// <summary> /// Select the job to which the trigger is associated. /// </summary> /// <param name="conn">the DB Connection</param> /// <param name="triggerName">the name of the trigger</param> /// <param name="groupName">the group containing the trigger</param> /// <param name="loadHelper">The load helper.</param> /// <returns>The <see cref="JobDetail" /> object associated with the given trigger</returns> public virtual JobDetail SelectJobForTrigger(ConnectionAndTransactionHolder conn, string triggerName, string groupName, ITypeLoadHelper loadHelper) { using (IDbCommand cmd = PrepareCommand(conn, ReplaceTablePrefix(SqlSelectJobForTrigger))) { AddCommandParameter(cmd, 1, "triggerName", triggerName); AddCommandParameter(cmd, 2, "triggerGroup", groupName); using (IDataReader rs = cmd.ExecuteReader()) { if (rs.Read()) { JobDetail job = new JobDetail(); job.Name = GetString(rs[0]); job.Group = GetString(rs[1]); job.Durable = GetBoolean(rs[2]); job.JobType = loadHelper.LoadType(GetString(rs[3])); job.RequestsRecovery = GetBoolean(rs[4]); return job; } else { if (logger.IsDebugEnabled) { logger.Debug("No job for trigger '" + groupName + "." + triggerName + "'."); } return null; } } } }
/// <summary> /// Update the base trigger data. /// </summary> /// <param name="conn">The DB Connection.</param> /// <param name="trigger">The trigger to insert.</param> /// <param name="state">The state that the trigger should be stored in.</param> /// <param name="jobDetail">The job detail.</param> /// <returns>The number of rows updated.</returns> public virtual int UpdateTrigger(ConnectionAndTransactionHolder conn, Trigger trigger, string state, JobDetail jobDetail) { // save some clock cycles by unnecessarily writing job data blob ... bool updateJobData = trigger.JobDataMap.Dirty; byte[] baos = null; if (updateJobData && trigger.JobDataMap.Count > 0) { baos = SerializeJobData(trigger.JobDataMap); } IDbCommand cmd; int insertResult; if (updateJobData) { cmd = PrepareCommand(conn, ReplaceTablePrefix(SqlUpdateTrigger)); } else { cmd = PrepareCommand(conn, ReplaceTablePrefix(SqlUpdateTriggerSkipData)); } AddCommandParameter(cmd, 1, "triggerJobName", trigger.JobName); AddCommandParameter(cmd, 2, "triggerJobGroup", trigger.JobGroup); AddCommandParameter(cmd, 3, "triggerVolatile", GetDbBooleanValue(trigger.Volatile)); AddCommandParameter(cmd, 4, "triggerDescription", trigger.Description); long nextFireTime = -1; if (trigger.GetNextFireTimeUtc().HasValue) { nextFireTime = trigger.GetNextFireTimeUtc().Value.Ticks; } AddCommandParameter(cmd, 5, "triggerNextFireTime", Convert.ToDecimal(nextFireTime)); long prevFireTime = -1; if (trigger.GetPreviousFireTimeUtc().HasValue) { prevFireTime = trigger.GetPreviousFireTimeUtc().Value.Ticks; } AddCommandParameter(cmd, 6, "triggerPreviousFireTime", Convert.ToDecimal(prevFireTime)); AddCommandParameter(cmd, 7, "triggerState", state); string paramName = "triggerType"; if (trigger is SimpleTrigger && !trigger.HasAdditionalProperties) { // UpdateSimpleTrigger(conn, (SimpleTrigger)trigger); AddCommandParameter(cmd, 8, paramName, TriggerTypeSimple); } else if (trigger is CronTrigger && !trigger.HasAdditionalProperties) { // UpdateCronTrigger(conn, (CronTrigger)trigger); AddCommandParameter(cmd, 8, paramName, TriggerTypeCron); } else { // UpdateBlobTrigger(conn, trigger); AddCommandParameter(cmd, 8, paramName, TriggerTypeBlob); } AddCommandParameter(cmd, 9, "triggerStartTime", Convert.ToDecimal(trigger.StartTimeUtc.Ticks)); long endTime = 0; if (trigger.EndTimeUtc.HasValue) { endTime = trigger.EndTimeUtc.Value.Ticks; } AddCommandParameter(cmd, 10, "triggerEndTime", Convert.ToDecimal(endTime)); AddCommandParameter(cmd, 11, "triggerCalendarName", trigger.CalendarName); AddCommandParameter(cmd, 12, "triggerMisfireInstruction", trigger.MisfireInstruction); AddCommandParameter(cmd, 13, "triggerPriority", trigger.Priority); paramName = "triggerJobJobDataMap"; if (updateJobData) { if (baos != null) { AddCommandParameter(cmd, 14, paramName, baos, dbProvider.Metadata.DbBinaryType); } else { AddCommandParameter(cmd, 14, paramName, null, dbProvider.Metadata.DbBinaryType); } AddCommandParameter(cmd, 15, "triggerName", trigger.Name); AddCommandParameter(cmd, 16, "triggerGroup", trigger.Group); } else { AddCommandParameter(cmd, 14, "triggerName", trigger.Name); AddCommandParameter(cmd, 15, "triggerGroup", trigger.Group); } insertResult = cmd.ExecuteNonQuery(); if (insertResult > 0) { DeleteTriggerListeners(conn, trigger.Name, trigger.Group); String[] trigListeners = trigger.TriggerListenerNames; for (int i = 0; trigListeners != null && i < trigListeners.Length; i++) { InsertTriggerListener(conn, trigger, trigListeners[i]); } } return insertResult; }
/// <summary> /// Checks equality between given job detail and this instance. /// </summary> /// <param name="detail">The detail to compare this instance with.</param> /// <returns></returns> public bool Equals(JobDetail detail) { return IsEqual(detail); }
/// <summary> /// Determines whether the specified detail is equal to this instance. /// </summary> /// <param name="detail">The detail to examine.</param> /// <returns> /// <c>true</c> if the specified detail is equal; otherwise, <c>false</c>. /// </returns> protected virtual bool IsEqual(JobDetail detail) { //doesn't consider job's saved data, //durability etc return (detail != null) && (detail.Name == Name) && (detail.Group == Group) && (detail.JobType == JobType); }
/// <summary> <p> /// Create a <see cref="ObjectAlreadyExistsException" /> and auto-generate a /// message using the name/group from the given <see cref="JobDetail" />. /// </p> /// /// <p> /// The message will read: <br />"Unable to store Job with name: '__' and /// group: '__', because one already exists with this identification." /// </p> /// </summary> public ObjectAlreadyExistsException(JobDetail offendingJob) : base( string.Format(CultureInfo.InvariantCulture, "Unable to store Job with name: '{0}' and group: '{1}', because one already exists with this identification.", offendingJob.Name, offendingJob.Group)) { }