From 0982530569cdd2ea58fb8bafb02c668eafe61f18 Mon Sep 17 00:00:00 2001
From: Niko Ehrenfeuchter <nikolaus.ehrenfeuchter@unibas.ch>
Date: Wed, 14 Feb 2018 16:00:19 +0100
Subject: [PATCH] Allow configuration to be spread across two separate files.

One file ("config.common.xml") containing site-global configuration
settings, the other one (the file name equals the host name with a
".xml" suffix) containing host-specific settings.

Both files use the same syntax and may contain the same setting
attributes with the host-specific one having priority over the
site-global one.

Refers to #18
---
 ATxCommon/Serializables/ServiceConfig.cs | 34 ++++++++++++++++++++----
 ATxConfigTest/AutoTxConfigTest.cs        |  7 +++--
 2 files changed, 32 insertions(+), 9 deletions(-)

diff --git a/ATxCommon/Serializables/ServiceConfig.cs b/ATxCommon/Serializables/ServiceConfig.cs
index acc1bc8..601240e 100644
--- a/ATxCommon/Serializables/ServiceConfig.cs
+++ b/ATxCommon/Serializables/ServiceConfig.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Configuration;
 using System.IO;
 using System.Xml;
+using System.Xml.Linq;
 using System.Xml.Serialization;
 using NLog;
 
@@ -33,15 +34,38 @@ namespace ATxCommon.Serializables
             throw new SettingsPropertyIsReadOnlyException("The config file must not be written by the service!");
         }
 
-        public static ServiceConfig Deserialize(string file) {
-            Log.Debug("Trying to read service configuration XML file: [{0}]", file);
-            var serializer = new XmlSerializer(typeof(ServiceConfig));
+        /// <summary>
+        /// Load the host specific and the common XML configuration files, combine them and
+        /// deserialize them into a ServiceConfig object. The host specific configuration file's
+        /// name is defined as the hostname with an ".xml" suffix.
+        /// </summary>
+        /// <param name="path">The path to the configuration files.</param>
+        /// <returns>A ServiceConfig object with validated settings.</returns>
+        public static ServiceConfig Deserialize(string path) {
             ServiceConfig config;
-            using (var reader = XmlReader.Create(file)) {
+
+            var commonFile = Path.Combine(path, "config.common.xml");
+            var specificFile = Path.Combine(path, Environment.MachineName + ".xml");
+
+            // for parsing the configuration from two separate files we are using the default
+            // behaviour of the .NET XmlSerializer on duplicates: only the first occurrence is
+            // used, all other ones are silentley being discarded - this way we simply append the
+            // contents of the common config file to the host-specific and deserialize then:
+            var common = XElement.Load(commonFile);
+            Log.Debug("Loaded common configuration XML file: [{0}]", commonFile);
+            var combined = XElement.Load(specificFile);
+            Log.Debug("Loaded host specific configuration XML file: [{0}]", specificFile);
+            combined.Add(common.Nodes());
+            Log.Trace("Combined XML structure:\n\n{0}\n\n", combined);
+
+            using (var reader = XmlReader.Create(new StringReader(combined.ToString()))) {
+                Log.Debug("Trying to parse combined XML.");
+                var serializer = new XmlSerializer(typeof(ServiceConfig));
                 config = (ServiceConfig) serializer.Deserialize(reader);
             }
+
             ValidateConfiguration(config);
-            Log.Debug("Finished deserializing service configuration XML file.");
+            Log.Debug("Successfully parsed and validated configuration XML.");
             return config;
         }
 
diff --git a/ATxConfigTest/AutoTxConfigTest.cs b/ATxConfigTest/AutoTxConfigTest.cs
index 679659e..641af12 100644
--- a/ATxConfigTest/AutoTxConfigTest.cs
+++ b/ATxConfigTest/AutoTxConfigTest.cs
@@ -21,7 +21,7 @@ namespace ATxConfigTest
                 Layout = @"${date:format=yyyy-MM-dd HH\:mm\:ss} [${level}] (${logger}) ${message}",
             };
             logConfig.AddTarget("console", consoleTarget);
-            var logRuleConsole = new LoggingRule("*", LogLevel.Debug, consoleTarget);
+            var logRuleConsole = new LoggingRule("*", LogLevel.Trace, consoleTarget);
             logConfig.LoggingRules.Add(logRuleConsole);
             LogManager.Configuration = logConfig;
 
@@ -29,13 +29,12 @@ namespace ATxConfigTest
             if (args.Length > 0)
                 baseDir = args[0];
 
-            var configPath = Path.Combine(baseDir, "configuration.xml");
             var statusPath = Path.Combine(baseDir, "status.xml");
 
             try {
                 string msg;
-                Console.WriteLine($"\nTrying to parse configuration file [{configPath}]...\n");
-                _config = ServiceConfig.Deserialize(configPath);
+                Console.WriteLine($"\nTrying to parse configuration files from [{baseDir}]...\n");
+                _config = ServiceConfig.Deserialize(baseDir);
                 msg = "------------------ configuration settings ------------------";
                 Console.WriteLine($"{msg}\n{_config.Summary()}{msg}\n");
 
-- 
GitLab