/// <summary>
 /// Sends a message to Spark with version info, etc
 /// If any exceptions occur, they will be logged and ignored
 /// </summary>
 private static void SendVersionUpdateNotifications()
 {
     try
     {
         using (var rockContext = new RockContext())
         {
             Rock.Utility.SparkLinkHelper.SendToSpark(rockContext);
         }
     }
     catch (Exception ex)
     {
         // Just catch any exceptions, log it, and keep moving...
         try
         {
             var startupException = new RockStartupException("Error sending version update notifications", ex);
             LogError(startupException, null);
             ExceptionLogService.LogException(startupException, null);
         }
         catch
         {
             // ignore
         }
     }
 }
        /// <summary>
        /// Starts the job scheduler.
        /// </summary>
        private static void StartJobScheduler()
        {
            using (var rockContext = new RockContext())
            {
                // create scheduler
                ISchedulerFactory schedulerFactory = new StdSchedulerFactory();
                QuartzScheduler = schedulerFactory.GetScheduler();

                // get list of active jobs
                ServiceJobService jobService = new ServiceJobService(rockContext);
                var activeJobList            = jobService.GetActiveJobs().OrderBy(a => a.Name).ToList();
                foreach (ServiceJob job in activeJobList)
                {
                    const string ErrorLoadingStatus = "Error Loading Job";

                    try
                    {
                        IJobDetail jobDetail  = jobService.BuildQuartzJob(job);
                        ITrigger   jobTrigger = jobService.BuildQuartzTrigger(job);

                        // Schedule the job (unless the cron expression is set to never run for an on-demand job like rebuild streaks)
                        if (job.CronExpression != ServiceJob.NeverScheduledCronExpression)
                        {
                            QuartzScheduler.ScheduleJob(jobDetail, jobTrigger);
                        }

                        //// if the last status was an error, but we now loaded successful, clear the error
                        // also, if the last status was 'Running', clear that status because it would have stopped if the app restarted
                        if (job.LastStatus == ErrorLoadingStatus || job.LastStatus == "Running")
                        {
                            job.LastStatusMessage = string.Empty;
                            job.LastStatus        = string.Empty;
                            rockContext.SaveChanges();
                        }
                    }
                    catch (Exception exception)
                    {
                        // create a friendly error message
                        string message = $"Error loading the job: {job.Name}.\n\n{exception.Message}";

                        // log the error
                        var startupException = new RockStartupException(message, exception);

                        LogError(startupException, null);

                        job.LastStatusMessage = message;
                        job.LastStatus        = ErrorLoadingStatus;
                        job.LastStatus        = ErrorLoadingStatus;
                        rockContext.SaveChanges();

                        var jobHistoryService = new ServiceJobHistoryService(rockContext);
                        var jobHistory        = new ServiceJobHistory()
                        {
                            ServiceJobId  = job.Id,
                            StartDateTime = RockDateTime.Now,
                            StopDateTime  = RockDateTime.Now,
                            Status        = job.LastStatus,
                            StatusMessage = job.LastStatusMessage
                        };

                        jobHistoryService.Add(jobHistory);
                        rockContext.SaveChanges();
                    }
                }

                // set up the listener to report back from jobs as they complete
                QuartzScheduler.ListenerManager.AddJobListener(new RockJobListener(), EverythingMatcher <JobKey> .AllJobs());

                // set up a trigger listener that can prevent a job from running if another scheduler is
                // already running it (i.e., someone running it manually).
                QuartzScheduler.ListenerManager.AddTriggerListener(new RockTriggerListener(), EverythingMatcher <JobKey> .AllTriggers());

                // start the scheduler
                QuartzScheduler.Start();
            }
        }
        /// <summary>
        /// Runs the plugin migrations for the specified plugin assembly
        /// </summary>
        /// <param name="pluginAssembly">The plugin assembly.</param>
        /// <returns></returns>
        /// <exception cref="RockStartupException">
        /// The '{assemblyName}' plugin assembly contains duplicate migration numbers ({ migrationNumberAttr.Number}).
        /// or
        /// ##Plugin Migration error occurred in {assemblyMigrations.Key}, {migrationType.Value.Name}##
        /// </exception>
        public static bool RunPluginMigrations(Assembly pluginAssembly)
        {
            string pluginAssemblyName = pluginAssembly.GetName().Name;

            // Migrate any plugins from the plugin assembly that have pending migrations
            List <Type> pluginMigrationTypes = Rock.Reflection.SearchAssembly(pluginAssembly, typeof(Rock.Plugin.Migration)).Select(a => a.Value).ToList();

            // If any plugin migrations types were found
            if (!pluginMigrationTypes.Any())
            {
                return(false);
            }

            bool result = false;

            // Get the current rock version
            var rockVersion = new Version(Rock.VersionInfo.VersionInfo.GetRockProductVersionNumber());

            // put the migrations to run in a Dictionary so that we can run them in the correct order
            // based on MigrationNumberAttribute
            var migrationTypesByNumber = new Dictionary <int, Type>();

            // Iterate plugin migrations
            foreach (var migrationType in pluginMigrationTypes)
            {
                // Get the MigrationNumberAttribute for the migration
                var migrationNumberAttr = migrationType.GetCustomAttribute <Rock.Plugin.MigrationNumberAttribute>();
                if (migrationNumberAttr != null)
                {
                    // If the migration's minimum Rock version is less than or equal to the current rock version, add it to the list
                    var minRockVersion = new Version(migrationNumberAttr.MinimumRockVersion);
                    if (minRockVersion.CompareTo(rockVersion) <= 0)
                    {
                        // Check to make sure no another migration has same number
                        if (migrationTypesByNumber.ContainsKey(migrationNumberAttr.Number))
                        {
                            throw new RockStartupException($"The '{pluginAssemblyName}' plugin assembly contains duplicate migration numbers ({ migrationNumberAttr.Number}).");
                        }

                        migrationTypesByNumber.Add(migrationNumberAttr.Number, migrationType);
                    }
                }
            }

            // Create EF service for plugin migrations
            var rockContext            = new RockContext();
            var pluginMigrationService = new PluginMigrationService(rockContext);

            // Get the versions that have already been installed
            var installedMigrationNumbers = pluginMigrationService.Queryable()
                                            .Where(m => m.PluginAssemblyName == pluginAssemblyName)
                                            .Select(a => a.MigrationNumber);

            // narrow it down to migrations that haven't already been installed
            migrationTypesByNumber = migrationTypesByNumber
                                     .Where(a => !installedMigrationNumbers.Contains(a.Key))
                                     .ToDictionary(k => k.Key, v => v.Value);

            // Iterate each migration in the assembly in MigrationNumber order
            var migrationTypesToRun = migrationTypesByNumber.OrderBy(a => a.Key).Select(a => a.Value).ToList();

            var configConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings["RockContext"]?.ConnectionString;

            try
            {
                using (var sqlConnection = new SqlConnection(configConnectionString))
                {
                    try
                    {
                        sqlConnection.Open();
                    }
                    catch (SqlException ex)
                    {
                        throw new RockStartupException("Error connecting to the SQL database. Please check the 'RockContext' connection string in the web.ConnectionString.config file.", ex);
                    }

                    // Iterate thru each plugin migration in this assembly, if one fails, will log the exception and stop running migrations for this assembly
                    foreach (Type migrationType in migrationTypesToRun)
                    {
                        int migrationNumber = migrationType.GetCustomAttribute <Rock.Plugin.MigrationNumberAttribute>().Number;

                        using (var sqlTxn = sqlConnection.BeginTransaction())
                        {
                            bool transactionActive = true;
                            try
                            {
                                // Create an instance of the migration and run the up migration
                                var migration = Activator.CreateInstance(migrationType) as Rock.Plugin.Migration;
                                migration.SqlConnection  = sqlConnection;
                                migration.SqlTransaction = sqlTxn;
                                migration.Up();
                                sqlTxn.Commit();
                                transactionActive = false;

                                // Save the plugin migration version so that it is not run again
                                var pluginMigration = new PluginMigration();
                                pluginMigration.PluginAssemblyName = pluginAssemblyName;
                                pluginMigration.MigrationNumber    = migrationNumber;
                                pluginMigration.MigrationName      = migrationType.Name;
                                pluginMigrationService.Add(pluginMigration);
                                rockContext.SaveChanges();

                                result = true;
                            }
                            catch (Exception ex)
                            {
                                if (transactionActive)
                                {
                                    sqlTxn.Rollback();
                                }

                                throw new RockStartupException($"##Plugin Migration error occurred in { migrationNumber}, {migrationType.Name}##", ex);
                            }
                        }
                    }
                }
            }
            catch (RockStartupException rockStartupException)
            {
                // if a plugin migration got an error, it gets wrapped with a RockStartupException
                // If this occurs, we'll log the migration that occurred,  and stop running migrations for this assembly
                System.Diagnostics.Debug.WriteLine(rockStartupException.Message);
                LogError(rockStartupException, null);
            }
            catch (Exception ex)
            {
                // If an exception occurs in an an assembly, log the error, and stop running migrations for this assembly
                var startupException = new RockStartupException($"Error running migrations from {pluginAssemblyName}");
                System.Diagnostics.Debug.WriteLine(startupException.Message);
                LogError(ex, null);
            }

            return(result);
        }
        /// <summary>
        /// Loads the cache objects.
        /// </summary>
        private static void LoadCacheObjects(RockContext rockContext)
        {
            // Cache all the entity types
            foreach (var entityType in new Rock.Model.EntityTypeService(rockContext).Queryable().AsNoTracking())
            {
                EntityTypeCache.Get(entityType);
            }

            // Cache all the Field Types
            foreach (var fieldType in new Rock.Model.FieldTypeService(rockContext).Queryable().AsNoTracking())
            {
                // improve performance of loading FieldTypeCache by doing LoadAttributes using an existing rockContext before doing FieldTypeCache.Get to avoid calling LoadAttributes with new context per FieldTypeCache
                fieldType.LoadAttributes(rockContext);
                FieldTypeCache.Get(fieldType);
            }

            var all = FieldTypeCache.All();

            // Read all the qualifiers first so that EF doesn't perform a query for each attribute when it's cached
            var qualifiers = new Dictionary <int, Dictionary <string, string> >();

            foreach (var attributeQualifier in new Rock.Model.AttributeQualifierService(rockContext).Queryable().AsNoTracking())
            {
                try
                {
                    if (!qualifiers.ContainsKey(attributeQualifier.AttributeId))
                    {
                        qualifiers.Add(attributeQualifier.AttributeId, new Dictionary <string, string>());
                    }

                    qualifiers[attributeQualifier.AttributeId].Add(attributeQualifier.Key, attributeQualifier.Value);
                }
                catch (Exception ex)
                {
                    var startupException = new RockStartupException("Error loading cache objects", ex);
                    LogError(startupException, null);
                }
            }

            // Cache all the attributes, except for user preferences
            var attributeQuery = new Rock.Model.AttributeService(rockContext).Queryable("Categories");
            int?personUserValueEntityTypeId = EntityTypeCache.GetId(Person.USER_VALUE_ENTITY);

            if (personUserValueEntityTypeId.HasValue)
            {
                attributeQuery = attributeQuery.Where(a => !a.EntityTypeId.HasValue || a.EntityTypeId.Value != personUserValueEntityTypeId);
            }

            foreach (var attribute in attributeQuery.AsNoTracking().ToList())
            {
                // improve performance of loading AttributeCache by doing LoadAttributes using an existing rockContext before doing AttributeCache.Get to avoid calling LoadAttributes with new context per AttributeCache
                attribute.LoadAttributes(rockContext);

                if (qualifiers.ContainsKey(attribute.Id))
                {
                    Rock.Web.Cache.AttributeCache.Get(attribute, qualifiers[attribute.Id]);
                }
                else
                {
                    Rock.Web.Cache.AttributeCache.Get(attribute, new Dictionary <string, string>());
                }
            }

            // Force authorizations to be cached
            Rock.Security.Authorization.Get();
        }