/// <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(); }