Add BizTalk endpoint reachability monitoring
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace BizTalkCheckmkPulse
|
||||
{
|
||||
/// <summary>
|
||||
/// Liest und schreibt die lokale Endpoint-Konfiguration atomar und mit strikter Validierung.
|
||||
/// </summary>
|
||||
internal sealed class EndpointCatalogStore
|
||||
{
|
||||
private const string RootName = "BizTalkEndpointCatalog";
|
||||
private const string Version = "1";
|
||||
private readonly string _path;
|
||||
private readonly int _maxBytes;
|
||||
private readonly int _maxEntries;
|
||||
|
||||
public EndpointCatalogStore(string path, int maxBytes, int maxEntries)
|
||||
{
|
||||
_path = path;
|
||||
_maxBytes = maxBytes;
|
||||
_maxEntries = maxEntries;
|
||||
}
|
||||
|
||||
public EndpointCatalog Read(string environmentName)
|
||||
{
|
||||
var info = new FileInfo(_path);
|
||||
if (!info.Exists)
|
||||
{
|
||||
throw new FileNotFoundException("Endpoint catalog does not exist.", _path);
|
||||
}
|
||||
|
||||
if (info.Length <= 0 || info.Length > _maxBytes)
|
||||
{
|
||||
throw new InvalidDataException("Endpoint catalog size is outside the configured range.");
|
||||
}
|
||||
|
||||
XDocument document;
|
||||
var settings = new XmlReaderSettings
|
||||
{
|
||||
DtdProcessing = DtdProcessing.Prohibit,
|
||||
XmlResolver = null,
|
||||
MaxCharactersInDocument = _maxBytes
|
||||
};
|
||||
using (var stream = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete))
|
||||
using (var reader = XmlReader.Create(stream, settings))
|
||||
{
|
||||
document = XDocument.Load(reader, LoadOptions.None);
|
||||
}
|
||||
|
||||
var root = document.Root;
|
||||
if (root == null
|
||||
|| root.Name.LocalName != RootName
|
||||
|| ReadAttribute(root, "version") != Version)
|
||||
{
|
||||
throw new InvalidDataException("Endpoint catalog format or version is invalid.");
|
||||
}
|
||||
|
||||
var machine = ReadAttribute(root, "machine");
|
||||
if (!string.Equals(machine, Environment.MachineName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidDataException("Endpoint catalog was created for a different machine.");
|
||||
}
|
||||
|
||||
var catalogEnvironment = ReadAttribute(root, "environment");
|
||||
if (!string.Equals(catalogEnvironment, environmentName ?? string.Empty, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidDataException("Endpoint catalog was created for a different environment.");
|
||||
}
|
||||
|
||||
DateTime synchronizedUtc;
|
||||
if (!DateTime.TryParseExact(
|
||||
ReadAttribute(root, "synchronizedUtc"),
|
||||
"o",
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
|
||||
out synchronizedUtc))
|
||||
{
|
||||
throw new InvalidDataException("Endpoint catalog synchronization timestamp is invalid.");
|
||||
}
|
||||
|
||||
var catalog = new EndpointCatalog
|
||||
{
|
||||
MachineName = machine,
|
||||
EnvironmentName = catalogEnvironment,
|
||||
SynchronizedUtc = synchronizedUtc,
|
||||
ActiveCandidates = ReadInt(root, "activeCandidates", 0, _maxEntries),
|
||||
UnsupportedCandidates = ReadInt(root, "unsupportedCandidates", 0, _maxEntries)
|
||||
};
|
||||
|
||||
foreach (var node in root.Elements("Endpoint"))
|
||||
{
|
||||
if (catalog.Entries.Count >= _maxEntries)
|
||||
{
|
||||
throw new InvalidDataException("Endpoint catalog exceeds EndpointMaxCount.");
|
||||
}
|
||||
|
||||
var entry = new EndpointCatalogEntry
|
||||
{
|
||||
Key = ReadAttribute(node, "key"),
|
||||
ArtifactType = ReadAttribute(node, "artifactType"),
|
||||
ApplicationName = ReadAttribute(node, "application"),
|
||||
ArtifactName = ReadAttribute(node, "artifact"),
|
||||
TransportRole = ReadAttribute(node, "transportRole"),
|
||||
AdapterName = ReadAttribute(node, "adapter"),
|
||||
Protocol = ReadAttribute(node, "protocol").ToUpperInvariant(),
|
||||
Host = ReadAttribute(node, "host"),
|
||||
Port = ReadInt(node, "port", 1, 65535),
|
||||
Enabled = ReadBool(node, "enabled"),
|
||||
AutoDiscovered = ReadBool(node, "autoDiscovered")
|
||||
};
|
||||
ValidateEntry(entry);
|
||||
catalog.Entries.Add(entry);
|
||||
}
|
||||
|
||||
if (catalog.Entries.Select(x => x.Key).Distinct(StringComparer.OrdinalIgnoreCase).Count() != catalog.Entries.Count)
|
||||
{
|
||||
throw new InvalidDataException("Endpoint catalog contains duplicate keys.");
|
||||
}
|
||||
|
||||
return catalog;
|
||||
}
|
||||
|
||||
public void Write(EndpointCatalog catalog)
|
||||
{
|
||||
if (catalog == null)
|
||||
{
|
||||
throw new ArgumentNullException("catalog");
|
||||
}
|
||||
|
||||
if (catalog.Entries.Count > _maxEntries)
|
||||
{
|
||||
throw new InvalidDataException("Endpoint catalog exceeds EndpointMaxCount.");
|
||||
}
|
||||
|
||||
foreach (var entry in catalog.Entries)
|
||||
{
|
||||
ValidateEntry(entry);
|
||||
}
|
||||
|
||||
var root = new XElement(
|
||||
RootName,
|
||||
new XAttribute("version", Version),
|
||||
new XAttribute("machine", Environment.MachineName),
|
||||
new XAttribute("environment", catalog.EnvironmentName ?? string.Empty),
|
||||
new XAttribute("synchronizedUtc", catalog.SynchronizedUtc.ToUniversalTime().ToString("o", CultureInfo.InvariantCulture)),
|
||||
new XAttribute("activeCandidates", catalog.ActiveCandidates),
|
||||
new XAttribute("unsupportedCandidates", catalog.UnsupportedCandidates));
|
||||
foreach (var entry in catalog.Entries
|
||||
.OrderBy(x => x.ApplicationName, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(x => x.ArtifactType, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(x => x.ArtifactName, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(x => x.TransportRole, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
root.Add(new XElement(
|
||||
"Endpoint",
|
||||
new XAttribute("key", entry.Key),
|
||||
new XAttribute("artifactType", entry.ArtifactType ?? string.Empty),
|
||||
new XAttribute("application", entry.ApplicationName ?? string.Empty),
|
||||
new XAttribute("artifact", entry.ArtifactName ?? string.Empty),
|
||||
new XAttribute("transportRole", entry.TransportRole ?? string.Empty),
|
||||
new XAttribute("adapter", entry.AdapterName ?? string.Empty),
|
||||
new XAttribute("protocol", entry.Protocol),
|
||||
new XAttribute("host", entry.Host),
|
||||
new XAttribute("port", entry.Port),
|
||||
new XAttribute("enabled", entry.Enabled),
|
||||
new XAttribute("autoDiscovered", entry.AutoDiscovered)));
|
||||
}
|
||||
|
||||
var document = new XDocument(new XDeclaration("1.0", "utf-8", null), root);
|
||||
byte[] bytes;
|
||||
using (var memory = new MemoryStream())
|
||||
using (var writer = XmlWriter.Create(memory, new XmlWriterSettings
|
||||
{
|
||||
Encoding = new UTF8Encoding(false),
|
||||
Indent = true,
|
||||
NewLineChars = "\r\n",
|
||||
NewLineHandling = NewLineHandling.Replace
|
||||
}))
|
||||
{
|
||||
document.Save(writer);
|
||||
writer.Flush();
|
||||
bytes = memory.ToArray();
|
||||
}
|
||||
|
||||
if (bytes.Length > _maxBytes)
|
||||
{
|
||||
throw new InvalidDataException("Endpoint catalog exceeds EndpointCatalogMaxBytes.");
|
||||
}
|
||||
|
||||
AtomicWrite(bytes);
|
||||
}
|
||||
|
||||
private void AtomicWrite(byte[] bytes)
|
||||
{
|
||||
var directory = Path.GetDirectoryName(_path);
|
||||
if (string.IsNullOrWhiteSpace(directory))
|
||||
{
|
||||
throw new InvalidOperationException("Endpoint catalog path has no parent directory.");
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(directory);
|
||||
var temporaryPath = Path.Combine(directory, Path.GetFileName(_path) + "." + Guid.NewGuid().ToString("N") + ".tmp");
|
||||
try
|
||||
{
|
||||
using (var stream = new FileStream(temporaryPath, FileMode.CreateNew, FileAccess.Write, FileShare.None, 4096, FileOptions.WriteThrough))
|
||||
{
|
||||
stream.Write(bytes, 0, bytes.Length);
|
||||
stream.Flush(true);
|
||||
}
|
||||
|
||||
if (File.Exists(_path))
|
||||
{
|
||||
File.Replace(temporaryPath, _path, null, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
File.Move(temporaryPath, _path);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(temporaryPath))
|
||||
{
|
||||
try { File.Delete(temporaryPath); } catch (IOException) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateEntry(EndpointCatalogEntry entry)
|
||||
{
|
||||
if (entry == null
|
||||
|| string.IsNullOrWhiteSpace(entry.Key)
|
||||
|| string.IsNullOrWhiteSpace(entry.ArtifactType)
|
||||
|| string.IsNullOrWhiteSpace(entry.ArtifactName)
|
||||
|| string.IsNullOrWhiteSpace(entry.Host)
|
||||
|| entry.Host.IndexOfAny(new[] { '\r', '\n', '\t', ' ' }) >= 0
|
||||
|| entry.Port < 1
|
||||
|| entry.Port > 65535
|
||||
|| !(string.Equals(entry.Protocol, "TCP", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(entry.Protocol, "UDP", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
throw new InvalidDataException("Endpoint catalog contains an invalid entry.");
|
||||
}
|
||||
}
|
||||
|
||||
private static string ReadAttribute(XElement element, string name)
|
||||
{
|
||||
var attribute = element.Attribute(name);
|
||||
if (attribute == null)
|
||||
{
|
||||
throw new InvalidDataException("Endpoint catalog attribute is missing: " + name);
|
||||
}
|
||||
|
||||
return attribute.Value.Trim();
|
||||
}
|
||||
|
||||
private static int ReadInt(XElement element, string name, int min, int max)
|
||||
{
|
||||
int value;
|
||||
if (!int.TryParse(ReadAttribute(element, name), NumberStyles.Integer, CultureInfo.InvariantCulture, out value)
|
||||
|| value < min
|
||||
|| value > max)
|
||||
{
|
||||
throw new InvalidDataException("Endpoint catalog integer is invalid: " + name);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static bool ReadBool(XElement element, string name)
|
||||
{
|
||||
bool value;
|
||||
if (!bool.TryParse(ReadAttribute(element, name), out value))
|
||||
{
|
||||
throw new InvalidDataException("Endpoint catalog boolean is invalid: " + name);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user