/// <summary>
        /// Gets the property groups.
        /// </summary>
        /// <param name="prefix">The prefix.</param>
        /// <returns></returns>
		public virtual string[] GetPropertyGroups(string prefix)
		{
            HashSet groups = new HashSet(10);

			if (!prefix.EndsWith("."))
			{
				prefix += ".";
			}

			foreach (string key in props.Keys)
			{
				if (key.StartsWith(prefix))
				{
					string groupName = key.Substring(prefix.Length, (key.IndexOf('.', prefix.Length)) - (prefix.Length));
					groups.Add(groupName);
				}
			}

            return (string[]) groups.ToArray(typeof (string));
		}
        protected override TriggerPropertyBundle GetTriggerPropertyBundle(SimplePropertiesTriggerProperties props)
        {
            int repeatCount = (int)props.Long1;
            int interval = props.Int1;
            string intervalUnitStr = props.String1;
            string daysOfWeekStr = props.String2;
            string timeOfDayStr = props.String3;

            IntervalUnit intervalUnit = (IntervalUnit) Enum.Parse(typeof(IntervalUnit), intervalUnitStr, true);
            DailyTimeIntervalScheduleBuilder scheduleBuilder = DailyTimeIntervalScheduleBuilder.Create()
                .WithInterval(interval, intervalUnit)
                .WithRepeatCount(repeatCount);

            if (daysOfWeekStr != null)
            {
                ISet<DayOfWeek> daysOfWeek = new HashSet<DayOfWeek>();
                String[] nums = daysOfWeekStr.Split(',');
                if (nums.Length > 0)
                {
                    foreach (String num in nums)
                    {
                        daysOfWeek.Add((DayOfWeek) Int32.Parse(num));
                    }
                    scheduleBuilder.OnDaysOfTheWeek(daysOfWeek);
                }
            }
            else
            {
                scheduleBuilder.OnDaysOfTheWeek(DailyTimeIntervalScheduleBuilder.AllDaysOfTheWeek);
            }

            if (timeOfDayStr != null)
            {
                string[] nums = timeOfDayStr.Split(',');
                TimeOfDay startTimeOfDay = null;
                if (nums.Length >= 3)
                {
                    int hour = Int32.Parse(nums[0]);
                    int min = Int32.Parse(nums[1]);
                    int sec = Int32.Parse(nums[2]);
                    startTimeOfDay = new TimeOfDay(hour, min, sec);
                }
                else
                {
                    startTimeOfDay = TimeOfDay.HourMinuteAndSecondOfDay(0, 0, 0);
                }
                scheduleBuilder.StartingDailyAt(startTimeOfDay);

                TimeOfDay endTimeOfDay;
                if (nums.Length >= 6)
                {
                    int hour = Int32.Parse(nums[3]);
                    int min = Int32.Parse(nums[4]);
                    int sec = Int32.Parse(nums[5]);
                    endTimeOfDay = new TimeOfDay(hour, min, sec);
                }
                else
                {
                    endTimeOfDay = TimeOfDay.HourMinuteAndSecondOfDay(23, 59, 59);
                }
                scheduleBuilder.EndingDailyAt(endTimeOfDay);
            }
            else
            {
                scheduleBuilder.StartingDailyAt(TimeOfDay.HourMinuteAndSecondOfDay(0, 0, 0));
                scheduleBuilder.EndingDailyAt(TimeOfDay.HourMinuteAndSecondOfDay(23, 59, 59));
            }
            

            int timesTriggered = props.Int2;
            String[] statePropertyNames = {"timesTriggered"};
            Object[] statePropertyValues = {timesTriggered};

            return new TriggerPropertyBundle(scheduleBuilder, statePropertyNames, statePropertyValues);
        }
        /// <summary>
        /// Selects the paused trigger groups.
        /// </summary>
        /// <param name="conn">The DB Connection.</param>
        /// <returns></returns>
        public virtual ISet SelectPausedTriggerGroups(ConnectionAndTransactionHolder conn)
        {
            HashSet retValue = new HashSet();


            using (IDbCommand cmd = PrepareCommand(conn, ReplaceTablePrefix(SqlSelectPausedTriggerGroups)))
            {
                using (IDataReader dr = cmd.ExecuteReader())
                {
                    while (dr.Read())
                    {
                        string groupName = (string)dr[ColumnTriggerGroup];
                        retValue.Add(groupName);
                    }
                }
                return retValue;
            }
        }
        protected virtual void ClusterRecover(ConnectionAndTransactionHolder conn, IList failedInstances)
        {
            if (failedInstances.Count > 0)
            {
                long recoverIds = DateTime.UtcNow.Ticks;

                LogWarnIfNonZero(failedInstances.Count,
                                 "ClusterManager: detected " + failedInstances.Count + " failed or restarted instances.");
                try
                {
                    foreach (SchedulerStateRecord rec in failedInstances)
                    {
                        Log.Info("ClusterManager: Scanning for instance \"" + rec.SchedulerInstanceId +
                                 "\"'s failed in-progress jobs.");

                        IList firedTriggerRecs =
                            Delegate.SelectInstancesFiredTriggerRecords(conn, rec.SchedulerInstanceId);

                        int acquiredCount = 0;
                        int recoveredCount = 0;
                        int otherCount = 0;

                        ISet triggerKeys = new HashSet();

                        foreach (FiredTriggerRecord ftRec in firedTriggerRecs)
                        {
                            Key tKey = ftRec.TriggerKey;
                            Key jKey = ftRec.JobKey;

                            triggerKeys.Add(tKey);

                            // release blocked triggers..
                            if (ftRec.FireInstanceState.Equals(StateBlocked))
                            {
                                Delegate.UpdateTriggerStatesForJobFromOtherState(conn, jKey.Name, jKey.Group,
                                                                                 StateWaiting,
                                                                                 StateBlocked);
                            }
                            else if (ftRec.FireInstanceState.Equals(StatePausedBlocked))
                            {
                                Delegate.UpdateTriggerStatesForJobFromOtherState(conn, jKey.Name, jKey.Group,
                                                                                 StatePaused,
                                                                                 StatePausedBlocked);
                            }

                            // release acquired triggers..
                            if (ftRec.FireInstanceState.Equals(StateAcquired))
                            {
                                Delegate.UpdateTriggerStateFromOtherState(conn, tKey.Name, tKey.Group, StateWaiting,
                                                                          StateAcquired);
                                acquiredCount++;
                            }
                            else if (ftRec.JobRequestsRecovery)
                            {
                                // handle jobs marked for recovery that were not fully
                                // executed..
                                if (JobExists(conn, jKey.Name, jKey.Group))
                                {
                                    DateTime tempAux = new DateTime(ftRec.FireTimestamp);
                                    SimpleTrigger rcvryTrig =
                                        new SimpleTrigger(
                                            "recover_" + rec.SchedulerInstanceId + "_" + Convert.ToString(recoverIds++, CultureInfo.InvariantCulture),
                                            SchedulerConstants.DefaultRecoveryGroup, tempAux);
                                    rcvryTrig.Volatile = ftRec.TriggerIsVolatile;
                                    rcvryTrig.JobName = jKey.Name;
                                    rcvryTrig.JobGroup = jKey.Group;
                                    rcvryTrig.MisfireInstruction = MisfireInstruction.SimpleTrigger.FireNow;
                                    rcvryTrig.Priority = ftRec.Priority;
                                    JobDataMap jd = Delegate.SelectTriggerJobDataMap(conn, tKey.Name, tKey.Group);
                                    jd.Put(SchedulerConstants.FailedJobOriginalTriggerName, tKey.Name);
                                    jd.Put(SchedulerConstants.FailedJobOriginalTriggerGroup, tKey.Group);
                                    jd.Put(SchedulerConstants.FailedJobOriginalTriggerFiretimeInMillisecoonds, Convert.ToString(ftRec.FireTimestamp, CultureInfo.InvariantCulture));
                                    rcvryTrig.JobDataMap = jd;

                                    rcvryTrig.ComputeFirstFireTimeUtc(null);
                                    StoreTrigger(conn, null, rcvryTrig, null, false, StateWaiting, false, true);
                                    recoveredCount++;
                                }
                                else
                                {
                                    Log.Warn("ClusterManager: failed job '" + jKey +
                                             "' no longer exists, cannot schedule recovery.");
                                    otherCount++;
                                }
                            }
                            else
                            {
                                otherCount++;
                            }

                            // free up stateful job's triggers
                            if (ftRec.JobIsStateful)
                            {
                                Delegate.UpdateTriggerStatesForJobFromOtherState(conn, jKey.Name, jKey.Group,
                                                                                 StateWaiting,
                                                                                 StateBlocked);
                                Delegate.UpdateTriggerStatesForJobFromOtherState(conn, jKey.Name, jKey.Group,
                                                                                 StatePaused,
                                                                                 StatePausedBlocked);
                            }
                        }

                        Delegate.DeleteFiredTriggers(conn, rec.SchedulerInstanceId);


                        // Check if any of the fired triggers we just deleted were the last fired trigger
                        // records of a COMPLETE trigger.
                        int completeCount = 0;
                        foreach (Key triggerKey in triggerKeys)
                        {
                            if (
                                Delegate.SelectTriggerState(conn, triggerKey.Name, triggerKey.Group).Equals(
                                    StateComplete))
                            {
                                IList firedTriggers =
                                    Delegate.SelectFiredTriggerRecords(conn, triggerKey.Name, triggerKey.Group);
                                if (firedTriggers.Count == 0)
                                {
                                    SchedulingContext schedulingContext = new SchedulingContext();
                                    schedulingContext.InstanceId = instanceId;

                                    if (RemoveTrigger(conn, schedulingContext, triggerKey.Name, triggerKey.Group))
                                    {
                                        completeCount++;
                                    }
                                }
                            }
                        }
                        LogWarnIfNonZero(acquiredCount,
                                         "ClusterManager: ......Freed " + acquiredCount + " acquired trigger(s).");
                        LogWarnIfNonZero(completeCount,
                                         "ClusterManager: ......Deleted " + completeCount + " complete triggers(s).");
                        LogWarnIfNonZero(recoveredCount,
                                         "ClusterManager: ......Scheduled " + recoveredCount +
                                         " recoverable job(s) for recovery.");
                        LogWarnIfNonZero(otherCount,
                                         "ClusterManager: ......Cleaned-up " + otherCount + " other failed job(s).");


                        if (rec.SchedulerInstanceId.Equals(InstanceId) == false)
                        {
                            Delegate.DeleteSchedulerState(conn, rec.SchedulerInstanceId);
                        }
                    }
                }
                catch (Exception e)
                {
                    throw new JobPersistenceException("Failure recovering jobs: " + e.Message, e);
                }
            }
        }
        /// <summary>
        /// Select the distinct instance names of all fired-trigger records.
        /// </summary>
        /// <param name="conn">The conn.</param>
        /// <returns></returns>
        /// <remarks>
        /// This is useful when trying to identify orphaned fired triggers (a
        /// fired trigger without a scheduler state record.)
        /// </remarks>
        public ISet SelectFiredTriggerInstanceNames(ConnectionAndTransactionHolder conn)
        {
            ISet instanceNames = new HashSet();
            using (IDbCommand cmd = PrepareCommand(conn, ReplaceTablePrefix(SqlSelectFiredTriggerInstanceNames)))
            {
                using (IDataReader rs = cmd.ExecuteReader())
                {
                    while (rs.Read())
                    {
                        instanceNames.Add(rs[ColumnInstanceName]);
                    }

                    return instanceNames;
                }
            }
        }