// Constructor with just AppHost parameter public BaseServicesData(IAppHost appHost) { Log.Debug("Entering BaseServicesData .ctor"); Container = appHost.GetContainer(); // At this point, appHost.AppSettings has all of the data read in from the various Configuration sources (environment, command line, indirect file, direct file, compiled in) // If the Redis configuration key exists, register Redis as a name:value pair cache if (appHost.AppSettings.Exists(configKeyPrefix + configKeyRedisConnectionString)) { var redisConnectionString = appHost.AppSettings.GetString(configKeyPrefix + configKeyRedisConnectionString); Container.Register <IRedisClientsManager>(c => new RedisManagerPool(redisConnectionString)); Container.Register(c => c.Resolve <IRedisClientsManager>().GetCacheClient()); cacheClient = Container.Resolve <ICacheClient>(); } else { throw new NotImplementedException(RedisConnectionStringKeyNotFoundExceptionMessage); } // Ensure that the cacheClient is running try { var test = cacheClient.GetKeysStartingWith(configKeyPrefix); } catch (ServiceStack.Redis.RedisException Ex) { if (Ex.InnerException.Message.Contains(ListeningServiceNotRunningInnerExceptionMessage)) { throw new Exception($"In BaseServicesData .ctor: {RedisNotRunningExceptionMessage} on {Ex.Message}", Ex); } } // ToDo: Validate the cache and the appSettings Config keys/values for the BaseService align // Key names in the cache for this namespace's configuration settings consist of this namespace and the string .Config // This prefix is available as a static method on this class var cacheConfigKeysForThisNamespace = cacheClient.GetKeysStartingWith(configKeyPrefix); //var appSettingsConfigKeysForThisNamespace = appHost.AppSettings.GetAllKeys().Select(x => x.IndexOf(configKeyPrefix) >= 0? x: configKeyPrefix + x); //ToDo: record any discrepancies, and report them in a log, and also report to the GUI when BaseServices get initialized. ///Temp: This stores the values from the AppSettings into the CacheClient (Redis currently) via the properties setter for the following Config settings MySqlConnectionString = appHost.AppSettings .GetString(configKeyPrefix + configKeyMySqlConnectionString); RedisCacheConnectionString = appHost.AppSettings .GetString(configKeyPrefix + configKeyRedisConnectionString); // ToDo: Instead of single keys, a magic function that reads the set of keys in each , recursively, and compares those sets, and the values of the keys present in both // At this point the Redis cache should match the current run's AppConfigurationSettings // Populate the ConfigurationData in the BaseServicesData instance ConstructConfigurationData(); // Populate the ComputerInventory structure with localhost data ConstructComputerInventory(); // Populate the UserData in the BaseServicesData instance ConstructUserData(); // Populate the AuthenticationData structure ConstructAuthenticationData(appHost); // Populate the AuthorizationData structure ConstructAuthorizationData(appHost); // ToDo: Move this region to Agent.BaseServices.Data.Timers // Add a dictionary of timers to the IoC container #region create a dictionary of timers and register it in the IoC container var timers = new Dictionary <string, System.Timers.Timer>(); Container.Register <Dictionary <string, System.Timers.Timer> >(c => timers); #endregion create a dictionary of timers and register it in the IoC container // Create a dictionary of tasks that is intended to hold "long running" tasks and register the dictionary in the IoC container ConstructLongRunningTasks(); // Add a timer to check the status of long running tasks, and attach a callback to the timer #region create longRunningTasksCheckTimer, connect callback, and store in container's timers Log.Debug("In BaseServicesData .ctor: creating longRunningTasksCheckTimer"); var longRunningTasksCheckTimer = new System.Timers.Timer(10000) { AutoReset = true }; longRunningTasksCheckTimer.Elapsed += new ElapsedEventHandler(LongRunningTasksCheckTimer_Elapsed); timers.Add(LongRunningTasksCheckTimerName, longRunningTasksCheckTimer); #endregion create longRunningTasksCheckTimer, connect callback, and store in container's timers // Populate the application's Base Gateways // Location of the files will depend on running as LifeCycle Production/QA/Dev as well as Debug and Release settings Gateways = new MultiGatewaysBuilder() // Command line flags have highest priority // next in priority are Environment variables //.AddEnvironmentalVariables() // next in priority are Configuration settings in a text file relative to the current working directory at the point in time when this method executes. //.AddTextFile(pluginGatewaysTextFileName) // Builtin (compiled in) have the lowest priority //.AddDictionarySettings(DefaultGateways.Configuration()) .Build(); // temporary manually populate the collection with one Gateway // Build a Gateway entry for Google Maps GeoCode and ReverseGeoCode REST endpoints var geb = new GatewayEntryBuilder(); geb.AddName("ReverseGeoCode"); geb.AddRUri("geocode/json"); geb.AddReqDataPayloadType(typeof(Base_GoogleMapsGeoCoding_ReverseGeoCode_ReqDTO)); geb.AddRspDataPayloadType(typeof(Base_GoogleMapsGeoCoding_ReverseGeoCode_RspDTO)); var ge = geb.Build(); var defaultPolicy = Policy.Handle <Exception>().RetryAsync(3, (exception, attempt) => { // This is the exception handler for this defaultPolicy Log.Debug($"Policy logging: {exception.Message} : attempt = {attempt}"); //retries++; }); // Build a Gateway for Google Maps GeoCode Gateway // ToDo replace DefaultAPIKEy auth with a more robust and extendable solution string defaultAPIKey = appHost.AppSettings .GetString(configKeyPrefix + configKeyGoogleMapsAPIKey); var gb = new GatewayBuilder(); gb.AddName("GoogleMaps"); gb.AddBaseUri(new Uri("https://maps.googleapis.com/maps/api/")); //gb.AddDefaultPolicy(defaultPolicy); // ToDo replace DefaultAPIKEy auth with a more robust and extendable solution gb.AddDefaultAPIKey(defaultAPIKey); gb.AddGatewayEntry(ge); var gw = gb.Build(); Gateways.Add("GoogleMaps", gw); //Gateways.Add("GoogleMapsGeoCoding", new GatewayBuilder().AddName("GoogleMapsGeoCoding").AddBaseUri(new Uri("https://maps.googleapis.com/maps/api").AddDefaultPolicy(new Polly.Policy()).Build()); // Create a collection of GatewayMonitors for BaseServices based on the collection of Gateways defined by the Base services var gatewayMonitorsBuilder = new GatewayMonitorsBuilder("Base"); //gatewayMonitorsBuilder.AddGatewayMonitor(new GatewayMonitor(Gateways.Get("GoogleMapsGeoCoding"))); GatewayMonitors = gatewayMonitorsBuilder.Build(); // temporary manually populate the collection with one GatewayMonitor var gemb = new GatewayEntryMonitorBuilder(); gemb.AddName("GeoCode"); var gem = gemb.Build(); var gmb = new GatewayMonitorBuilder(); gmb.AddName("GoogleMapsGeoCoding"); gmb.AddGatewayEntryMonitor(gem); var gm = gmb.Build(); GatewayMonitors.GatewayMonitorColl.Add("Manual", gm); // GatewayMonitors.GatewayMonitorColl.Add("Manual", new GatewayMonitorBuilder().AddName("GoogleMapsGeoCoding").AddGatewayEntryMonitor(new GatewayEntryMonitorBuilder().AddName("GeoCode").Build()).Build()); // Store the collection of Gateway Monitor in the Base Data structure // Populate the specific per-user data instance for this user ConstructUserData(); // ToDo: support AppSettings to control the enable/disable of Postman // Enable Postman integration // AppHost.Plugins.Add(new PostmanFeature()); // ToDo: support AppSettings to control the enable/disable of CORS Feature // Enable CORS support appHost.Plugins.Add(new CorsFeature(allowedMethods: "GET, POST, PUT, DELETE, OPTIONS", allowedOrigins: "*", allowCredentials: true, allowedHeaders: "content-type, Authorization, Accept")); // ToDo: Document clearly the metadata feature cannot be allowed on /, because it messes up the default returning f the content of index.html appHost.Config.EnableFeatures = Feature.All.Remove(Feature.Metadata); // Turn debug mode for the ACEAgent depending if running in debug mode or release mode #if Debug appHost.Config.DebugMode = true; #else appHost.Config .DebugMode = false; #endif Log.Debug("Leaving BaseServicesData ctor"); }
public void Configure(IAppHost appHost) { Log.Debug("starting RealEstateServicesPlugin.Configure"); // Populate this Plugin's Application Configuration Settings // Location of the files will depend on running as LifeCycle Production/QA/Dev as well as Debug and Release settings var pluginAppSettings = new MultiAppSettingsBuilder() // Command line flags have highest priority // next in priority are Environment variables //.AddEnvironmentalVariables() // next in priority are Configuration settings in a text file relative to the current working directory at the point in time when this method executes. .AddTextFile(pluginSettingsTextFileName) // Builtin (compiled in) have the lowest priority .AddDictionarySettings(DefaultConfiguration.Configuration()) .Build(); // Key names in the cache for Configuration settings for a Plugin consist of the namespace and the string .Config // Key names in the appSettings of a Plugin for Configuration settings may or may not have the prefix // Compare the two lists of Configuration setting keys... // create a copy of the keys from AppSettings ensuring all are prefixed with the namespace and .Config var configKeyPrefix = myNamespace + ".Config."; var lengthConfigKeyPrefix = configKeyPrefix.Length; var appSettingsConfigKeys = pluginAppSettings.GetAllKeys(); var fullAppSettingsConfigKeys = appSettingsConfigKeys.Select(x => x.IndexOf(configKeyPrefix) >= 0? x: configKeyPrefix + x); // Get this namespaces configuration settings from the cache var cacheClient = appHost.GetContainer().Resolve <ICacheClient>(); var cacheConfigKeys = cacheClient.GetKeysStartingWith(configKeyPrefix); // Compare the configuration retrieved from the cache with the configuration from the built-in defaults, the text files, and the environment variables // If the cache is missing a ConfigKey, update the cache with the ConfigKey and its value from the appSettings var excludedConfigKeys = new HashSet <string>(cacheConfigKeys); var additionalConfigKeys = fullAppSettingsConfigKeys.Where(ck => !excludedConfigKeys.Contains(ck)); foreach (var ck in additionalConfigKeys) { cacheClient.Set <string>(ck, pluginAppSettings.GetString(ck.Substring(lengthConfigKeyPrefix))); } // If both the cache and the appSettings have the same ConfigKey, and the same value, do nothing // If both the cache and the appSettings have the same ConfigKey, and the values are different, // If the cache has a ConfigKey and the appSettings does not have that ConfigKey // Set a flag indicating the need to dialog with the user to resolve the cache vs. appSettings discrepancies bool configurationsMatch = true; // (appSettingsConfigkeys.Count == cacheConfigkeys.Count) ; // Populate this Plugin's Gateways // Location of the files will depend on running as LifeCycle Production/QA/Dev as well as Debug and Release settings var pluginGateways = new MultiGatewaysBuilder() // Command line flags have highest priority // next in priority are Environment variables //.AddEnvironmentalVariables() // next in priority are Configuration settings in a text file relative to the current working directory at the point in time when this method executes. //.AddTextFile(pluginGatewaysTextFileName) // Builtin (compiled in) have the lowest priority //.AddDictionarySettings(DefaultGateways.Configuration()) .Build(); // Create a Gateways collection from the txt file ConcurrentObservableDictionary <string, IGateway> gateways = new ConcurrentObservableDictionary <string, IGateway>(); gateways.Add("GoogleMapsGeoCoding", new GatewayBuilder().Build()); // Create the Plugin's data structure. There should only be a single instance. // Every Property matching a ConfigKey gets/sets the value of the matching ConfigKey in the cache // ConfigKey Properties do not have to be set in the constructor because the cache was setup before calling the constructor RealEstateServicesData RealEstateServicesData = new RealEstateServicesData(appHost, new ConcurrentObservableDictionary <string, decimal>(), onPluginRootCollectionChanged, onPluginRootPropertyChanged); // copy the most recent configuration settings to the Data // hmm should be a way to make sure the Data has a Property for each configuration setting, and to populate the initial Data with the cache value RealEstateServicesData.GoogleAPIKeyEncrypted = cacheClient.Get <string>(configKeyPrefix + "GoogleAPIKeyEncrypted"); RealEstateServicesData.HomeAwayAPIKeyEncrypted = cacheClient.Get <string>(configKeyPrefix + "HomeAwayAPIKeyEncrypted"); RealEstateServicesData.HomeAway_API_URI = cacheClient.Get <string>(configKeyPrefix + "HomeAway_API_URI"); RealEstateServicesData.Google_API_URI = cacheClient.Get <string>(configKeyPrefix + "Google_API_URI"); RealEstateServicesData.UriHomeAway_API_URI = cacheClient.Get <Uri>(configKeyPrefix + "UriHomeAway_API_URI"); RealEstateServicesData.UriGoogle_API_URI = cacheClient.Get <Uri>(configKeyPrefix + "UriGoogle_API_URI"); // and pass the Plugin's data structure to the container so it will be available to every other module and services appHost.GetContainer() .Register <RealEstateServicesData>(x => RealEstateServicesData); // ToDo: enable the mechanisms that monitors each GUI-specific data sensor, and start them running }