コード例 #1
0
        /// <inheritdoc />
        public override void SaveJobDetails(JobDetails jobDetails)
        {
            if (jobDetails == null)
            {
                throw new ArgumentNullException("jobDetails");
            }

            VersionedJobDetails versionedJobDetails = (VersionedJobDetails)jobDetails;

            lock (jobs)
            {
                ThrowIfDisposed();

                string jobName = jobDetails.JobSpec.Name;

                VersionedJobDetails existingJobDetails;
                if (!jobs.TryGetValue(jobName, out existingJobDetails))
                {
                    throw new ConcurrentModificationException(
                              "The job details could not be saved because the job was concurrently deleted.");
                }

                if (existingJobDetails.Version != versionedJobDetails.Version)
                {
                    throw new ConcurrentModificationException(
                              "The job details could not be saved because the job was concurrently modified.");
                }

                versionedJobDetails.Version += 1;
                jobs[jobName] = (VersionedJobDetails)versionedJobDetails.Clone();

                Monitor.PulseAll(jobs);
            }
        }
コード例 #2
0
        private void InternalUpdateJob(VersionedJobDetails existingJobDetails, JobSpec updatedJobSpec)
        {
            if (existingJobDetails.JobSpec.Name != updatedJobSpec.Name)
            {
                if (jobs.ContainsKey(updatedJobSpec.Name))
                {
                    throw new SchedulerException(String.Format(CultureInfo.CurrentCulture,
                                                               "Cannot rename job '{0}' to '{1}' because there already exists another job with the new name.",
                                                               existingJobDetails.JobSpec.Name, updatedJobSpec.Name));
                }

                jobs.Remove(existingJobDetails.JobSpec.Name);
                jobs.Add(updatedJobSpec.Name, existingJobDetails);
            }

            existingJobDetails.Version += 1;
            existingJobDetails.JobSpec  = updatedJobSpec.Clone();

            if (existingJobDetails.JobState == JobState.Scheduled)
            {
                existingJobDetails.JobState = JobState.Pending;
            }

            Monitor.PulseAll(jobs);
        }
コード例 #3
0
        /// <inheritdoc />
        protected override JobDetails GetNextJobToProcessOrWaitUntilSignaled(Guid schedulerGuid)
        {
            lock (syncRoot)
            {
                ThrowIfDisposed();

                DateTime timeBasisUtc = DateTime.UtcNow;
                DateTime?nextTriggerFireTimeUtc;
                try
                {
                    VersionedJobDetails nextJob = jobStoreDao.GetNextJobToProcess(clusterName, schedulerGuid, timeBasisUtc,
                                                                                  schedulerExpirationTimeInSeconds,
                                                                                  out nextTriggerFireTimeUtc);

                    if (nextJob != null)
                    {
                        return(nextJob);
                    }
                }
                catch (Exception ex)
                {
                    Logger.Warn(String.Format("The job store was unable to poll the database for the next job to process.  "
                                              + "It will try again in {0} seconds.", pollIntervalInSeconds), ex);
                    nextTriggerFireTimeUtc = null;
                }

                // Wait for a signal or the next fire time whichever comes first.
                if (nextTriggerFireTimeUtc.HasValue)
                {
                    // Update the time basis because the SP may have taken a non-trivial
                    // amount of time to run.
                    timeBasisUtc = DateTime.UtcNow;
                    if (nextTriggerFireTimeUtc.Value <= timeBasisUtc)
                    {
                        return(null);
                    }

                    // Need to ensure that wait time in millis will fit in a 32bit integer, otherwise the
                    // Monitor.Wait will throw an ArgumentException (even when using the TimeSpan based overload).
                    // This can happen when the next trigger fire time is very far out into the future like DateTime.MaxValue.
                    TimeSpan waitTimeSpan = nextTriggerFireTimeUtc.Value - timeBasisUtc;
                    int      waitMillis   = Math.Min((int)Math.Min(int.MaxValue, waitTimeSpan.TotalMilliseconds), pollIntervalInSeconds * 1000);
                    Monitor.Wait(syncRoot, waitMillis);
                }
                else
                {
                    Monitor.Wait(syncRoot, pollIntervalInSeconds * 1000);
                }
            }

            return(null);
        }
コード例 #4
0
        /// <inheritdoc />
        public override void SaveJobDetails(JobDetails jobDetails)
        {
            if (jobDetails == null)
            {
                throw new ArgumentNullException("jobStatus");
            }

            VersionedJobDetails versionedJobDetails = (VersionedJobDetails)jobDetails;

            ThrowIfDisposed();

            jobStoreDao.SaveJobDetails(clusterName, versionedJobDetails);
        }
コード例 #5
0
        /// <summary>
        /// Builds a job details object from the result set returned by the spSCHED_GetJobDetails
        /// and spSCHED_GetNextJob stored procedures.
        /// </summary>
        /// <param name="reader">The reader for the result set</param>
        /// <returns>The job details object</returns>
        protected virtual VersionedJobDetails BuildJobDetailsFromResultSet(IDataReader reader)
        {
            string  jobName        = reader.GetString(0);
            string  jobDescription = reader.GetString(1);
            string  jobKey         = reader.GetString(2);
            Trigger trigger        = (Trigger)DbUtils.DeserializeObject(DbUtils.MapDbValueToObject <byte[]>(reader.GetValue(3)));

            JobData  jobData                = (JobData)DbUtils.DeserializeObject(DbUtils.MapDbValueToObject <byte[]>(reader.GetValue(4)));
            DateTime creationTimeUtc        = DateTimeUtils.AssumeUniversalTime(reader.GetDateTime(5));
            JobState jobState               = (JobState)reader.GetInt32(6);
            DateTime?nextTriggerFireTimeUtc =
                DateTimeUtils.AssumeUniversalTime(DbUtils.MapDbValueToNullable <DateTime>(reader.GetValue(7)));
            int?     nextTriggerMisfireThresholdSeconds = DbUtils.MapDbValueToNullable <int>(reader.GetValue(8));
            TimeSpan?nextTriggerMisfireThreshold        = nextTriggerMisfireThresholdSeconds.HasValue
                                                                        ?
                                                          new TimeSpan(0, 0, nextTriggerMisfireThresholdSeconds.Value)
                                                                        : (TimeSpan?)null;

            Guid?    lastExecutionSchedulerGuid = DbUtils.MapDbValueToNullable <Guid>(reader.GetValue(9));
            DateTime?lastExecutionStartTimeUtc  =
                DateTimeUtils.AssumeUniversalTime(DbUtils.MapDbValueToNullable <DateTime>(reader.GetValue(10)));
            DateTime?lastExecutionEndTimeUtc =
                DateTimeUtils.AssumeUniversalTime(DbUtils.MapDbValueToNullable <DateTime>(reader.GetValue(11)));
            bool?  lastExecutionSucceeded     = DbUtils.MapDbValueToNullable <bool>(reader.GetValue(12));
            string lastExecutionStatusMessage = DbUtils.MapDbValueToObject <string>(reader.GetValue(13));

            int version = reader.GetInt32(14);

            JobSpec jobSpec = new JobSpec(jobName, jobDescription, jobKey, trigger);

            jobSpec.JobData = jobData;

            VersionedJobDetails details = new VersionedJobDetails(jobSpec, creationTimeUtc, version);

            details.JobState = jobState;
            details.NextTriggerFireTimeUtc      = nextTriggerFireTimeUtc;
            details.NextTriggerMisfireThreshold = nextTriggerMisfireThreshold;

            if (lastExecutionSchedulerGuid.HasValue && lastExecutionStartTimeUtc.HasValue)
            {
                JobExecutionDetails execution = new JobExecutionDetails(lastExecutionSchedulerGuid.Value,
                                                                        lastExecutionStartTimeUtc.Value);
                execution.EndTimeUtc    = lastExecutionEndTimeUtc;
                execution.Succeeded     = lastExecutionSucceeded.GetValueOrDefault();
                execution.StatusMessage = lastExecutionStatusMessage == null ? "" : lastExecutionStatusMessage;

                details.LastJobExecutionDetails = execution;
            }

            return(details);
        }
コード例 #6
0
        public void UpdateJob_IncrementsVersionNumber()
        {
            Mocks.ReplayAll();

            VersionedJobDetails jobDetails = (VersionedJobDetails)CreatePendingJob("job", DateTime.UtcNow);
            int originalVersion            = jobDetails.Version;

            jobDetails.JobSpec.Name = "renamedJob";
            JobStore.UpdateJob("job", jobDetails.JobSpec);

            VersionedJobDetails updatedJobDetails = (VersionedJobDetails)JobStore.GetJobDetails("renamedJob");

            Assert.AreEqual(originalVersion + 1, updatedJobDetails.Version,
                            "Version number of saved object should be incremented in database.");
        }
コード例 #7
0
        public void SaveJobDetails_IncrementsVersionNumber()
        {
            Mocks.ReplayAll();

            VersionedJobDetails jobDetails = (VersionedJobDetails)CreatePendingJob("job", DateTime.UtcNow);
            int originalVersion            = jobDetails.Version;

            jobDetails.JobState = JobState.Stopped;
            JobStore.SaveJobDetails(jobDetails);

            Assert.AreEqual(originalVersion + 1, jobDetails.Version,
                            "Version number of original object should be incremented in place.");

            VersionedJobDetails updatedJobDetails = (VersionedJobDetails)JobStore.GetJobDetails("job");

            Assert.AreEqual(originalVersion + 1, updatedJobDetails.Version,
                            "Version number of saved object should be incremented in memory too.");
        }
コード例 #8
0
        /// <inheritdoc />
        public override bool CreateJob(JobSpec jobSpec, DateTime creationTimeUtc, CreateJobConflictAction conflictAction)
        {
            if (jobSpec == null)
            {
                throw new ArgumentNullException("jobSpec");
            }
            if (!Enum.IsDefined(typeof(CreateJobConflictAction), conflictAction))
            {
                throw new ArgumentOutOfRangeException("conflictAction");
            }

            lock (jobs)
            {
                ThrowIfDisposed();

                VersionedJobDetails existingJobDetails;
                if (jobs.TryGetValue(jobSpec.Name, out existingJobDetails))
                {
                    switch (conflictAction)
                    {
                    case CreateJobConflictAction.Ignore:
                        return(false);

                    case CreateJobConflictAction.Update:
                        InternalUpdateJob(existingJobDetails, jobSpec);
                        return(true);

                    case CreateJobConflictAction.Replace:
                        break;

                    case CreateJobConflictAction.Throw:
                        throw new SchedulerException(String.Format(CultureInfo.CurrentCulture,
                                                                   "There is already a job with name '{0}'.", jobSpec.Name));
                    }
                }

                VersionedJobDetails jobDetails = new VersionedJobDetails(jobSpec.Clone(), creationTimeUtc, 0);

                jobs[jobSpec.Name] = jobDetails;
                Monitor.PulseAll(jobs);
                return(true);
            }
        }
コード例 #9
0
ファイル: MemoryJobStore.cs プロジェクト: aozora/arashi
        /// <inheritdoc />
        public override bool CreateJob(JobSpec jobSpec, DateTime creationTimeUtc, CreateJobConflictAction conflictAction)
        {
            if (jobSpec == null)
                throw new ArgumentNullException("jobSpec");
            if (!Enum.IsDefined(typeof (CreateJobConflictAction), conflictAction))
                throw new ArgumentOutOfRangeException("conflictAction");

            lock (jobs)
            {
                ThrowIfDisposed();

                VersionedJobDetails existingJobDetails;
                if (jobs.TryGetValue(jobSpec.Name, out existingJobDetails))
                {
                    switch (conflictAction)
                    {
                        case CreateJobConflictAction.Ignore:
                            return false;

                        case CreateJobConflictAction.Update:
                            InternalUpdateJob(existingJobDetails, jobSpec);
                            return true;

                        case CreateJobConflictAction.Replace:
                            break;

                        case CreateJobConflictAction.Throw:
                            throw new SchedulerException(String.Format(CultureInfo.CurrentCulture,
                                                                       "There is already a job with name '{0}'.", jobSpec.Name));
                    }
                }

                VersionedJobDetails jobDetails = new VersionedJobDetails(jobSpec.Clone(), creationTimeUtc, 0);

                jobs[jobSpec.Name] = jobDetails;
                Monitor.PulseAll(jobs);
                return true;
            }
        }
コード例 #10
0
		/// <summary>
		/// Builds a job details object from the result set returned by the spSCHED_GetJobDetails
		/// and spSCHED_GetNextJob stored procedures.
		/// </summary>
		/// <param name="reader">The reader for the result set</param>
		/// <returns>The job details object</returns>
		protected virtual VersionedJobDetails BuildJobDetailsFromResultSet(IDataReader reader)
		{
			string jobName = reader.GetString(0);
			string jobDescription = reader.GetString(1);
			string jobKey = reader.GetString(2);
			Trigger trigger = (Trigger) DbUtils.DeserializeObject(DbUtils.MapDbValueToObject<byte[]>(reader.GetValue(3)));

			JobData jobData = (JobData) DbUtils.DeserializeObject(DbUtils.MapDbValueToObject<byte[]>(reader.GetValue(4)));
			DateTime creationTimeUtc = DateTimeUtils.AssumeUniversalTime(reader.GetDateTime(5));
			JobState jobState = (JobState) reader.GetInt32(6);
			DateTime? nextTriggerFireTimeUtc =
				DateTimeUtils.AssumeUniversalTime(DbUtils.MapDbValueToNullable<DateTime>(reader.GetValue(7)));
			int? nextTriggerMisfireThresholdSeconds = DbUtils.MapDbValueToNullable<int>(reader.GetValue(8));
			TimeSpan? nextTriggerMisfireThreshold = nextTriggerMisfireThresholdSeconds.HasValue
			                                        	?
			                                        		new TimeSpan(0, 0, nextTriggerMisfireThresholdSeconds.Value)
			                                        	: (TimeSpan?) null;

			Guid? lastExecutionSchedulerGuid = DbUtils.MapDbValueToNullable<Guid>(reader.GetValue(9));
			DateTime? lastExecutionStartTimeUtc =
				DateTimeUtils.AssumeUniversalTime(DbUtils.MapDbValueToNullable<DateTime>(reader.GetValue(10)));
			DateTime? lastExecutionEndTimeUtc =
				DateTimeUtils.AssumeUniversalTime(DbUtils.MapDbValueToNullable<DateTime>(reader.GetValue(11)));
			bool? lastExecutionSucceeded = DbUtils.MapDbValueToNullable<bool>(reader.GetValue(12));
			string lastExecutionStatusMessage = DbUtils.MapDbValueToObject<string>(reader.GetValue(13));

			int version = reader.GetInt32(14);

			JobSpec jobSpec = new JobSpec(jobName, jobDescription, jobKey, trigger);
			jobSpec.JobData = jobData;

			VersionedJobDetails details = new VersionedJobDetails(jobSpec, creationTimeUtc, version);
			details.JobState = jobState;
			details.NextTriggerFireTimeUtc = nextTriggerFireTimeUtc;
			details.NextTriggerMisfireThreshold = nextTriggerMisfireThreshold;

			if (lastExecutionSchedulerGuid.HasValue && lastExecutionStartTimeUtc.HasValue)
			{
				JobExecutionDetails execution = new JobExecutionDetails(lastExecutionSchedulerGuid.Value,
				                                                        lastExecutionStartTimeUtc.Value);
				execution.EndTimeUtc = lastExecutionEndTimeUtc;
				execution.Succeeded = lastExecutionSucceeded.GetValueOrDefault();
				execution.StatusMessage = lastExecutionStatusMessage == null ? "" : lastExecutionStatusMessage;

				details.LastJobExecutionDetails = execution;
			}

			return details;
		}
コード例 #11
0
		/// <summary>
		/// Saves details for the job.
		/// </summary>
		/// <param name="clusterName">The cluster name, never null</param>
		/// <param name="jobDetails">The job details, never null</param>
		/// <exception cref="SchedulerException">Thrown if an error occurs</exception>
		public virtual void SaveJobDetails(string clusterName, VersionedJobDetails jobDetails)
		{
			try
			{
				using (IDbConnection connection = CreateConnection())
				{
					IDbCommand command = CreateStoredProcedureCommand(connection, "spSCHED_SaveJobDetails");

					AddInputParameter(command, "ClusterName", DbType.String, clusterName);

					AddInputParameter(command, "JobName", DbType.String, jobDetails.JobSpec.Name);
					AddInputParameter(command, "JobDescription", DbType.String, jobDetails.JobSpec.Description);
					AddInputParameter(command, "JobKey", DbType.String, jobDetails.JobSpec.JobKey);
					AddInputParameter(command, "TriggerObject", DbType.Binary,
					                  DbUtils.MapObjectToDbValue(DbUtils.SerializeObject(jobDetails.JobSpec.Trigger)));

					AddInputParameter(command, "JobDataObject", DbType.Binary,
					                  DbUtils.MapObjectToDbValue(DbUtils.SerializeObject(jobDetails.JobSpec.JobData)));
					AddInputParameter(command, "JobState", DbType.Int32, jobDetails.JobState);
					AddInputParameter(command, "NextTriggerFireTime", DbType.DateTime,
					                  DbUtils.MapNullableToDbValue(jobDetails.NextTriggerFireTimeUtc));
					int? nextTriggerMisfireThresholdSeconds = jobDetails.NextTriggerMisfireThreshold.HasValue
					                                          	?
					                                          		(int?) jobDetails.NextTriggerMisfireThreshold.Value.TotalSeconds
					                                          	: null;
					AddInputParameter(command, "NextTriggerMisfireThresholdSeconds", DbType.Int32,
					                  DbUtils.MapNullableToDbValue(nextTriggerMisfireThresholdSeconds));

					JobExecutionDetails execution = jobDetails.LastJobExecutionDetails;
					AddInputParameter(command, "LastExecutionSchedulerGUID", DbType.Guid,
					                  execution != null ? (object) execution.SchedulerGuid : DBNull.Value);
					AddInputParameter(command, "LastExecutionStartTime", DbType.DateTime,
					                  execution != null ? (object) execution.StartTimeUtc : DBNull.Value);
					AddInputParameter(command, "LastExecutionEndTime", DbType.DateTime,
					                  execution != null ? DbUtils.MapNullableToDbValue(execution.EndTimeUtc) : DBNull.Value);
					AddInputParameter(command, "LastExecutionSucceeded", DbType.Boolean,
					                  execution != null ? (object) execution.Succeeded : DBNull.Value);
					AddInputParameter(command, "LastExecutionStatusMessage", DbType.String,
					                  execution != null ? (object) execution.StatusMessage : DBNull.Value);

					AddInputParameter(command, "Version", DbType.Int32, jobDetails.Version);

					IDbDataParameter wasSavedParam = AddOutputParameter(command, "WasSaved", DbType.Boolean);

					connection.Open();
					command.ExecuteNonQuery();

					bool wasSaved = (bool) wasSavedParam.Value;
					if (!wasSaved)
						throw new ConcurrentModificationException(
							String.Format("Job '{0}' does not exist or was concurrently modified in the database.",
							              jobDetails.JobSpec.Name));

					jobDetails.Version += 1;
				}
			}
			catch (Exception ex)
			{
				throw new SchedulerException("The job store was unable to save job details to the database.", ex);
			}
		}
コード例 #12
0
        /// <summary>
        /// Saves details for the job.
        /// </summary>
        /// <param name="clusterName">The cluster name, never null</param>
        /// <param name="jobDetails">The job details, never null</param>
        /// <exception cref="SchedulerException">Thrown if an error occurs</exception>
        public virtual void SaveJobDetails(string clusterName, VersionedJobDetails jobDetails)
        {
            try
            {
                using (IDbConnection connection = CreateConnection())
                {
                    IDbCommand command = CreateStoredProcedureCommand(connection, "spSCHED_SaveJobDetails");

                    AddInputParameter(command, "ClusterName", DbType.String, clusterName);

                    AddInputParameter(command, "JobName", DbType.String, jobDetails.JobSpec.Name);
                    AddInputParameter(command, "JobDescription", DbType.String, jobDetails.JobSpec.Description);
                    AddInputParameter(command, "JobKey", DbType.String, jobDetails.JobSpec.JobKey);
                    AddInputParameter(command, "TriggerObject", DbType.Binary,
                                      DbUtils.MapObjectToDbValue(DbUtils.SerializeObject(jobDetails.JobSpec.Trigger)));

                    AddInputParameter(command, "JobDataObject", DbType.Binary,
                                      DbUtils.MapObjectToDbValue(DbUtils.SerializeObject(jobDetails.JobSpec.JobData)));
                    AddInputParameter(command, "JobState", DbType.Int32, jobDetails.JobState);
                    AddInputParameter(command, "NextTriggerFireTime", DbType.DateTime,
                                      DbUtils.MapNullableToDbValue(jobDetails.NextTriggerFireTimeUtc));
                    int?nextTriggerMisfireThresholdSeconds = jobDetails.NextTriggerMisfireThreshold.HasValue
                                                                                        ?
                                                             (int?)jobDetails.NextTriggerMisfireThreshold.Value.TotalSeconds
                                                                                        : null;
                    AddInputParameter(command, "NextTriggerMisfireThresholdSeconds", DbType.Int32,
                                      DbUtils.MapNullableToDbValue(nextTriggerMisfireThresholdSeconds));

                    JobExecutionDetails execution = jobDetails.LastJobExecutionDetails;
                    AddInputParameter(command, "LastExecutionSchedulerGUID", DbType.Guid,
                                      execution != null ? (object)execution.SchedulerGuid : DBNull.Value);
                    AddInputParameter(command, "LastExecutionStartTime", DbType.DateTime,
                                      execution != null ? (object)execution.StartTimeUtc : DBNull.Value);
                    AddInputParameter(command, "LastExecutionEndTime", DbType.DateTime,
                                      execution != null ? DbUtils.MapNullableToDbValue(execution.EndTimeUtc) : DBNull.Value);
                    AddInputParameter(command, "LastExecutionSucceeded", DbType.Boolean,
                                      execution != null ? (object)execution.Succeeded : DBNull.Value);
                    AddInputParameter(command, "LastExecutionStatusMessage", DbType.String,
                                      execution != null ? (object)execution.StatusMessage : DBNull.Value);

                    AddInputParameter(command, "Version", DbType.Int32, jobDetails.Version);

                    IDbDataParameter wasSavedParam = AddOutputParameter(command, "WasSaved", DbType.Boolean);

                    connection.Open();
                    command.ExecuteNonQuery();

                    bool wasSaved = (bool)wasSavedParam.Value;
                    if (!wasSaved)
                    {
                        throw new ConcurrentModificationException(
                                  String.Format("Job '{0}' does not exist or was concurrently modified in the database.",
                                                jobDetails.JobSpec.Name));
                    }

                    jobDetails.Version += 1;
                }
            }
            catch (Exception ex)
            {
                throw new SchedulerException("The job store was unable to save job details to the database.", ex);
            }
        }
コード例 #13
0
ファイル: MemoryJobStore.cs プロジェクト: QMTech/Attic
		private void InternalUpdateJob(VersionedJobDetails existingJobDetails, JobSpec updatedJobSpec)
		{
			if (existingJobDetails.JobSpec.Name != updatedJobSpec.Name)
			{
				if (jobs.ContainsKey(updatedJobSpec.Name))
					throw new SchedulerException(String.Format(CultureInfo.CurrentCulture,
					                                           "Cannot rename job '{0}' to '{1}' because there already exists another job with the new name.",
					                                           existingJobDetails.JobSpec.Name, updatedJobSpec.Name));

				jobs.Remove(existingJobDetails.JobSpec.Name);
				jobs.Add(updatedJobSpec.Name, existingJobDetails);
			}

			existingJobDetails.Version += 1;
			existingJobDetails.JobSpec = updatedJobSpec.Clone();

			if (existingJobDetails.JobState == JobState.Scheduled)
				existingJobDetails.JobState = JobState.Pending;

			Monitor.PulseAll(jobs);
		}
コード例 #14
0
		/// <summary>
		/// Saves details for the job.
		/// </summary>
		/// <param name="clusterName">The cluster name, never null</param>
		/// <param name="jobDetails">The job details, never null</param>
		/// <exception cref="SchedulerException">Thrown if an error occurs</exception>
		public virtual void SaveJobDetails(string clusterName, VersionedJobDetails jobDetails)
		{
			try
			{
				using (IDbConnection connection = CreateConnection())
				{
					connection.Open();
					using (IDbTransaction transaction = connection.BeginTransaction())
					{
						try
						{
							//
							// Look for a job with the existing name and version.
							//

							using (IDbCommand command = CreateCommand(connection, transaction, SelectVersionedJobCount))
							{
								AddInputParameter(command, "ClusterName", DbType.String, clusterName);
								AddInputParameter(command, "JobName", DbType.String, jobDetails.JobSpec.Name);
								AddInputParameter(command, "Version", DbType.Int32, jobDetails.Version);

								int jobCount = (int)command.ExecuteScalar();
								if (jobCount == 0)
								{
									throw new ConcurrentModificationException(
										String.Format("Job '{0}' does not exist or was concurrently modified in the database.",
													  jobDetails.JobSpec.Name));
								}
							}

							//
							// Update the job.
							//

							using (IDbCommand command = CreateCommand(connection, transaction, SaveJobDetailsUpdateJob))
							{
								AddInputParameter(command, "ClusterName", DbType.String, clusterName);

								AddInputParameter(command, "JobName", DbType.String, jobDetails.JobSpec.Name);
								AddInputParameter(command, "JobDescription", DbType.String, jobDetails.JobSpec.Description);
								AddInputParameter(command, "JobKey", DbType.String, jobDetails.JobSpec.JobKey);
								AddInputParameter(command, "TriggerObject", DbType.Binary,
												  DbUtils.MapObjectToDbValue(DbUtils.SerializeObject(jobDetails.JobSpec.Trigger)));

								AddInputParameter(command, "JobDataObject", DbType.Binary,
												  DbUtils.MapObjectToDbValue(DbUtils.SerializeObject(jobDetails.JobSpec.JobData)));
								AddInputParameter(command, "JobState", DbType.Int32, jobDetails.JobState);
								AddInputParameter(command, "NextTriggerFireTime", DbType.DateTime,
												  DbUtils.MapNullableToDbValue(jobDetails.NextTriggerFireTimeUtc));
								int? nextTriggerMisfireThresholdSeconds = jobDetails.NextTriggerMisfireThreshold.HasValue
																			? (int?)jobDetails.NextTriggerMisfireThreshold.Value.TotalSeconds
																			: null;
								AddInputParameter(command, "NextTriggerMisfireThresholdSeconds", DbType.Int32,
												  DbUtils.MapNullableToDbValue(nextTriggerMisfireThresholdSeconds));

								JobExecutionDetails execution = jobDetails.LastJobExecutionDetails;
								AddInputParameter(command, "LastExecutionSchedulerGUID", DbType.Guid,
												  execution != null ? (object)execution.SchedulerGuid : DBNull.Value);
								AddInputParameter(command, "LastExecutionStartTime", DbType.DateTime,
												  execution != null ? (object)execution.StartTimeUtc : DBNull.Value);
								AddInputParameter(command, "LastExecutionEndTime", DbType.DateTime,
												  execution != null ? DbUtils.MapNullableToDbValue(execution.EndTimeUtc) : DBNull.Value);
								AddInputParameter(command, "LastExecutionSucceeded", DbType.Boolean,
												  execution != null ? (object)execution.Succeeded : DBNull.Value);
								AddInputParameter(command, "LastExecutionStatusMessage", DbType.String,
												  execution != null ? (object)execution.StatusMessage : DBNull.Value);

								AddInputParameter(command, "Version", DbType.Int32, jobDetails.Version);

								command.ExecuteNonQuery();
							}

							transaction.Commit();

							jobDetails.Version += 1;
						}
						catch (Exception)
						{
							transaction.Rollback();
							throw;
						}
					}
				}
			}
			catch (Exception ex)
			{
				throw new SchedulerException("The job store was unable to save job details to the database.", ex);
			}
		}