Cause: - ResultsWriter required a generic way to generate customer-specific XML result data from TBF measurement results. - The customer reference XML contains example runtime values and repeated result structures, so it cannot be used directly as the generated output. - TBF result variables must be explicitly mapped to destinations in the customer XML structure. - The generated XML result data must support two output targets: - direct creation of an XML file, - delivery of the XML payload to a Microsoft SQL stored procedure. - Preview generation must allow the XML structure and configured mappings to be verified without executing the production database write. - Increase revision to 3.9.3145.100. Solution: 1. Added XML reference analysis - Creates a clean base XML structure. - Extracts the repeating result prototype. - Prevents sample runtime values from the customer reference XML from leaking into generated results. 2. Added configurable TBF-to-XML result mapping - Allows explicit mapping of TBF result variables to customer XML destinations. - Supports one-time and repeating XML destinations. - Keeps the mapping independent of the semantic meaning of customer XML attribute names. 3. Added XML destination viewer and configurator - Shows the current mapping. - Highlights repeating destinations. - Identifies already used one-time destinations. 4. Added runtime XML result generation - Uses the configured TBF-to-XML mappings. - Uses measurement procedure result data. - Builds the output from the clean base XML and repeating XML prototype. - Generates repeated result records according to the executed measurement procedure. 5. Added simulation-based Preview request - Generates a complete XML payload using simulation values. - Allows XML structure and mapping verification before production execution. - Does not execute the production stored procedure. 6. Added direct XML file output support to UniDataStorageWriter - Supports File / .xml as a physical output target. - Creates the generated XML result file in the configured output directory. 7. Added Microsoft SQL stored-procedure XML output - Supports Microsoft SQL / StoredProcedure as a physical output target. - Passes the generated XML payload through the configured stored procedure parameter. 8. Added optional XML payload archiving - Allows generated XML payloads to be stored in the configured Payload archive. - Can be used together with the Microsoft SQL stored-procedure output. 9. Kept ResultsWriter independent of the physical output target - ResultsWriter generates the result payload. - UniDataStorageWriter decides how and where the payload is physically written. - The same ResultsWriter XML generation mechanism is therefore used for both XML file and MSSQL outputs. 10. Increased revision - Updated revision to 3.9.3145.100.
1897 lines
63 KiB
C#
1897 lines
63 KiB
C#
using Common;
|
|
using FluentNHibernate.MappingModel.Output;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Windows.Forms;
|
|
using TBF.Rig.Generic;
|
|
using TBF.Rig.Output.DataStorage.UniDataStorageWriter.Interfaces;
|
|
using TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writers;
|
|
using static TBF.Rig.Output.DataStorage.UniDataStorageWriter.Types;
|
|
|
|
namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
|
|
{
|
|
/// <summary>
|
|
/// Provides configuration and diagnostic controls for
|
|
/// <see cref="Writer"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// The control configures the storage target, technology, write mode,
|
|
/// write templates and optional payload template settings.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Microsoft SQL configurations can use
|
|
/// <see cref="WriteMode.StoredProcedure"/>. In this mode an external XML
|
|
/// template can be selected through
|
|
/// <see cref="WriterCfg.PayloadTemplatePath"/> and the generated payload
|
|
/// can be passed to the parameter configured by
|
|
/// <see cref="WriterCfg.PayloadParameterName"/>.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Diagnostic input formats are:
|
|
/// </para>
|
|
/// <list type="bullet">
|
|
/// <item><description>Insert: <c>Column=Value</c>.</description></item>
|
|
/// <item><description>Update: <c>WhereName=WhereValue;SetName=SetValue</c>.</description></item>
|
|
/// <item><description>Stored procedure: <c>@Parameter|Type=Value</c>.</description></item>
|
|
/// </list>
|
|
///
|
|
/// <para>
|
|
/// Write template entries use the format <c>WriteMode|Template</c>.
|
|
/// For stored procedures the template body is the stored procedure name,
|
|
/// for example <c>StoredProcedure|dbo.sp_InsertDashboardResults_FF</c>.
|
|
/// </para>
|
|
/// </remarks>
|
|
public partial class WriterCfgCtrl : Configs.ConfigCtrlUtils, IComponentCfgCtrl
|
|
{
|
|
private readonly List<string> batchWriteLines = new List<string>();
|
|
|
|
private WriterCfg config;
|
|
|
|
private bool isUnlocked;
|
|
|
|
private const string CsvTemplateHint =
|
|
"Write template is not used.";
|
|
|
|
private const string DefaultDatabaseInsertTemplate =
|
|
"Insert|INSERT INTO dbo.table ({0}) VALUES ({1})";
|
|
|
|
private const string DefaultDatabaseUpdateTemplate =
|
|
"Update|UPDATE dbo.table SET {2} = {3} WHERE {0} = {1}";
|
|
|
|
private const string DefaultDatabaseStoredProcedureTemplate =
|
|
"StoredProcedure|dbo.StoredProcedureName";
|
|
|
|
private const string DefaultXlsxInsertTemplate =
|
|
"Insert|INSERT INTO Sheet1 ({0}) VALUES ({1})";
|
|
|
|
private const string DefaultXlsxUpdateTemplate =
|
|
"Update|UPDATE Sheet1 SET {2} = {3} WHERE {0} = {1}";
|
|
|
|
private const int EmSetCueBanner = 0x1501;
|
|
|
|
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
|
private static extern IntPtr SendMessage(
|
|
IntPtr hWnd,
|
|
int msg,
|
|
IntPtr wParam,
|
|
string lParam);
|
|
|
|
public bool ShowMore
|
|
{
|
|
get { return false; }
|
|
}
|
|
|
|
public IComponentCfg Config
|
|
{
|
|
get { return config as IComponentCfg; }
|
|
set
|
|
{
|
|
config = value as WriterCfg;
|
|
Redraw();
|
|
}
|
|
}
|
|
|
|
public WriterCfgCtrl()
|
|
{
|
|
InitializeComponent();
|
|
|
|
connectToDataSourceButton.Click += connectToDataSourceButton_Click;
|
|
writeDataByParamAndTemplateButton.Click += writeDataByParamAndTemplateButton_Click;
|
|
buttonAddParam.Click += buttonAddParam_Click;
|
|
buttonRemoveParam.Click += buttonRemoveParam_Click;
|
|
listBoxWriteParams.DoubleClick += listBoxWriteParams_DoubleClick;
|
|
writeModeComboBox.SelectedIndexChanged += writeModeComboBox_SelectedIndexChanged;
|
|
dataStorageTypeComboBox.SelectedIndexChanged += dataStorageTypeComboBox_SelectedIndexChanged;
|
|
|
|
buttonAddTemplate.Click += buttonAddTemplate_Click;
|
|
buttonUpdateTemplate.Click += buttonUpdateTemplate_Click;
|
|
buttonRemoveTemplate.Click += buttonRemoveTemplate_Click;
|
|
writeTemplatesListBox.SelectedIndexChanged += writeTemplatesListBox_SelectedIndexChanged;
|
|
|
|
technologyTypeComboBox.SelectedIndexChanged += technologyTypeComboBox_SelectedIndexChanged;
|
|
|
|
browsePayloadTemplateButton.Click += browsePayloadTemplateButton_Click;
|
|
browsePayloadArchiveButton.Click += browsePayloadArchiveButton_Click;
|
|
archivePayloadCheckBox.CheckedChanged += archivePayloadCheckBox_CheckedChanged;
|
|
|
|
//
|
|
// Diagnostic XML payloads can be multiline. Keep the compact
|
|
// control layout but preserve all pasted CR/LF characters.
|
|
//
|
|
writeParamValueTextBox.Multiline = true;
|
|
writeParamValueTextBox.AcceptsReturn = true;
|
|
writeParamValueTextBox.AcceptsTab = true;
|
|
writeParamValueTextBox.WordWrap = false;
|
|
writeParamValueTextBox.ScrollBars = ScrollBars.Both;
|
|
}
|
|
|
|
private void WriterCfgCtrl_Load(object sender, EventArgs e)
|
|
{
|
|
writeModeComboBox.Items.Clear();
|
|
writeModeComboBox.Items.Add(WriteMode.Insert.ToString());
|
|
writeModeComboBox.Items.Add(WriteMode.Update.ToString());
|
|
writeModeComboBox.Items.Add(WriteMode.StoredProcedure.ToString());
|
|
|
|
if (writeModeComboBox.Items.Count > 0)
|
|
writeModeComboBox.SelectedIndex = 0;
|
|
|
|
UpdateInputHint();
|
|
|
|
if (config != null)
|
|
Redraw();
|
|
|
|
ApplyTechnologyConfiguration(true);
|
|
}
|
|
|
|
public void Closing()
|
|
{
|
|
}
|
|
|
|
private void Redraw()
|
|
{
|
|
if (config == null)
|
|
return;
|
|
|
|
classNameLabel.Text = config.Factory.ClassName;
|
|
nameTextBox.Text = config.Name;
|
|
dataSourceTextBox.Text = config.DataSource;
|
|
payloadTemplatePathTextBox.Text = config.PayloadTemplatePath ?? string.Empty;
|
|
payloadParameterNameTextBox.Text = config.PayloadParameterName ?? string.Empty;
|
|
archivePayloadCheckBox.Checked = config.ArchivePayload;
|
|
payloadArchivePathTextBox.Text = config.PayloadArchivePath ?? string.Empty;
|
|
|
|
dataStorageTypeComboBox.Items.Clear();
|
|
|
|
ICollection<string> values = config.ParamValues(0);
|
|
if (values != null)
|
|
{
|
|
foreach (string item in values)
|
|
dataStorageTypeComboBox.Items.Add(item);
|
|
}
|
|
|
|
dataStorageTypeComboBox.Text = config.DataStorageType;
|
|
|
|
UpdateTechnologyTypeUi();
|
|
|
|
if (!string.IsNullOrWhiteSpace(config.TechnologyType))
|
|
{
|
|
if (technologyTypeComboBox.Items.Contains(config.TechnologyType))
|
|
technologyTypeComboBox.SelectedItem = config.TechnologyType;
|
|
else
|
|
technologyTypeComboBox.Text = config.TechnologyType;
|
|
}
|
|
else
|
|
{
|
|
technologyTypeComboBox.Text = string.Empty;
|
|
}
|
|
|
|
writeModeComboBox.SelectedItem = config.WriteMode.ToString();
|
|
|
|
if (writeModeComboBox.SelectedIndex < 0)
|
|
writeModeComboBox.SelectedItem = WriteMode.Insert.ToString();
|
|
|
|
RefreshTemplatesListBox();
|
|
UpdateInputHint();
|
|
RefreshWriteParamsListBox();
|
|
|
|
ApplyTechnologyConfiguration(true);
|
|
ApplyPayloadConfiguration();
|
|
}
|
|
|
|
public void Unlock()
|
|
{
|
|
isUnlocked = true;
|
|
|
|
nameTextBox.Enabled = true;
|
|
dataStorageTypeComboBox.Enabled = true;
|
|
dataSourceTextBox.Enabled = true;
|
|
|
|
sourceTestResultTextBox.Enabled = true;
|
|
sourceTestResultTextBox.ReadOnly = true;
|
|
|
|
writeTestResultTextBox.Enabled = true;
|
|
writeTestResultTextBox.ReadOnly = true;
|
|
|
|
writeParamValueTextBox.Enabled = true;
|
|
listBoxWriteParams.Enabled = true;
|
|
buttonAddParam.Enabled = true;
|
|
buttonRemoveParam.Enabled = true;
|
|
writeModeComboBox.Enabled = true;
|
|
technologyTypeComboBox.Enabled = true;
|
|
|
|
ApplyTechnologyConfiguration(false);
|
|
ApplyPayloadConfiguration();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates source hints, default templates and template control availability
|
|
/// according to the selected technology.
|
|
/// </summary>
|
|
/// <param name="initializeDefaults">
|
|
/// <see langword="true"/> to create default templates when the template list
|
|
/// is empty; otherwise, <see langword="false"/>.
|
|
/// </param>
|
|
private void ApplyTechnologyConfiguration(bool initializeDefaults)
|
|
{
|
|
string technologyType =
|
|
(technologyTypeComboBox.Text ?? string.Empty).Trim();
|
|
|
|
SetDataSourceHint(
|
|
GetDataSourceHint(
|
|
technologyType));
|
|
|
|
bool xmlFileTechnology =
|
|
technologyType ==
|
|
TechnologyTypes.Xml;
|
|
|
|
//
|
|
// XML file output always receives one complete generated document.
|
|
// SQL-style write modes and write templates are therefore not used.
|
|
//
|
|
if (xmlFileTechnology)
|
|
{
|
|
if (writeModeComboBox.Items.Contains(
|
|
WriteMode.Insert.ToString()))
|
|
{
|
|
writeModeComboBox.SelectedItem =
|
|
WriteMode.Insert.ToString();
|
|
}
|
|
|
|
writeModeComboBox.Enabled =
|
|
false;
|
|
|
|
writeTemplatesListBox.Items.Clear();
|
|
|
|
templateEditTextBox.Text =
|
|
"Write template is not used for XML payload files.";
|
|
|
|
SetTemplateControlsEnabled(
|
|
false);
|
|
|
|
UpdateInputHint();
|
|
ApplyPayloadConfiguration();
|
|
|
|
return;
|
|
}
|
|
|
|
writeModeComboBox.Enabled =
|
|
isUnlocked;
|
|
|
|
bool templatesSupported =
|
|
technologyType ==
|
|
TechnologyTypes.MicrosoftSql ||
|
|
technologyType ==
|
|
TechnologyTypes.Xlsx ||
|
|
technologyType ==
|
|
TechnologyTypes.Xls;
|
|
|
|
if (technologyType ==
|
|
TechnologyTypes.Csv)
|
|
{
|
|
writeTemplatesListBox.Items.Clear();
|
|
|
|
templateEditTextBox.Text =
|
|
CsvTemplateHint;
|
|
|
|
SetTemplateControlsEnabled(
|
|
false);
|
|
|
|
UpdateInputHint();
|
|
ApplyPayloadConfiguration();
|
|
|
|
return;
|
|
}
|
|
|
|
if (initializeDefaults &&
|
|
templatesSupported &&
|
|
writeTemplatesListBox.Items.Count == 0)
|
|
{
|
|
AddDefaultTemplates(
|
|
technologyType);
|
|
}
|
|
|
|
if (!templatesSupported)
|
|
{
|
|
writeTemplatesListBox.Items.Clear();
|
|
templateEditTextBox.Clear();
|
|
}
|
|
else if (templateEditTextBox.Text ==
|
|
CsvTemplateHint ||
|
|
templateEditTextBox.Text ==
|
|
"Write template is not used for XML payload files.")
|
|
{
|
|
templateEditTextBox.Clear();
|
|
}
|
|
|
|
SetTemplateControlsEnabled(
|
|
templatesSupported &&
|
|
isUnlocked);
|
|
|
|
UpdateInputHint();
|
|
ApplyPayloadConfiguration();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds default write templates for the selected technology.
|
|
/// </summary>
|
|
/// <param name="technologyType">
|
|
/// Selected writer technology type.
|
|
/// </param>
|
|
private void AddDefaultTemplates(string technologyType)
|
|
{
|
|
writeTemplatesListBox.Items.Clear();
|
|
|
|
switch (technologyType)
|
|
{
|
|
case TechnologyTypes.MicrosoftSql:
|
|
writeTemplatesListBox.Items.Add(
|
|
DefaultDatabaseInsertTemplate);
|
|
|
|
writeTemplatesListBox.Items.Add(
|
|
DefaultDatabaseUpdateTemplate);
|
|
|
|
writeTemplatesListBox.Items.Add(
|
|
DefaultDatabaseStoredProcedureTemplate);
|
|
break;
|
|
|
|
case TechnologyTypes.Xlsx:
|
|
case TechnologyTypes.Xls:
|
|
writeTemplatesListBox.Items.Add(
|
|
DefaultXlsxInsertTemplate);
|
|
|
|
writeTemplatesListBox.Items.Add(
|
|
DefaultXlsxUpdateTemplate);
|
|
break;
|
|
}
|
|
|
|
if (writeTemplatesListBox.Items.Count > 0)
|
|
writeTemplatesListBox.SelectedIndex = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enables or disables controls used for write template configuration.
|
|
/// </summary>
|
|
/// <param name="enabled">
|
|
/// <see langword="true"/> to enable template editing; otherwise,
|
|
/// <see langword="false"/>.
|
|
/// </param>
|
|
private void SetTemplateControlsEnabled(bool enabled)
|
|
{
|
|
writeTemplatesListBox.Enabled = enabled;
|
|
templateEditTextBox.Enabled = enabled;
|
|
|
|
buttonAddTemplate.Enabled = enabled;
|
|
buttonUpdateTemplate.Enabled = enabled;
|
|
buttonRemoveTemplate.Enabled = enabled;
|
|
|
|
examples1Button.Enabled = enabled;
|
|
}
|
|
|
|
public CfgUpdateFlags VerifyCfg(ref string message)
|
|
{
|
|
return CfgUpdateFlags.None;
|
|
}
|
|
|
|
public CfgUpdateFlags UpdateCfg()
|
|
{
|
|
CfgUpdateFlags flags = CfgUpdateFlags.None;
|
|
|
|
if (config == null)
|
|
return CfgUpdateFlags.Error;
|
|
|
|
WriteMode mode;
|
|
if (!Enum.TryParse(writeModeComboBox.Text, true, out mode))
|
|
mode = WriteMode.Insert;
|
|
|
|
if (!string.Equals(config.Name, nameTextBox.Text, StringComparison.Ordinal))
|
|
{
|
|
config.Name = nameTextBox.Text;
|
|
flags |= (CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
|
|
}
|
|
|
|
if (config.WriteMode != mode)
|
|
{
|
|
config.WriteMode = mode;
|
|
flags |= (CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
|
|
}
|
|
|
|
List<string> newTemplates = writeTemplatesListBox.Items.Cast<string>().ToList();
|
|
|
|
if (config.WriteTemplates == null ||
|
|
config.WriteTemplates.Count != newTemplates.Count ||
|
|
!config.WriteTemplates.SequenceEqual(newTemplates))
|
|
{
|
|
config.WriteTemplates = newTemplates;
|
|
flags |= (CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
|
|
}
|
|
|
|
flags |= UpdateDifferent(
|
|
ref config.DataStorageType,
|
|
dataStorageTypeComboBox.Text,
|
|
CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
|
|
|
|
flags |= UpdateDifferent(
|
|
ref config.TechnologyType,
|
|
technologyTypeComboBox.Text,
|
|
CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
|
|
|
|
flags |= UpdateDifferent(
|
|
ref config.DataSource,
|
|
dataSourceTextBox.Text,
|
|
CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
|
|
|
|
flags |= UpdateDifferent(
|
|
ref config.PayloadTemplatePath,
|
|
payloadTemplatePathTextBox.Text,
|
|
CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
|
|
|
|
flags |= UpdateDifferent(
|
|
ref config.PayloadParameterName,
|
|
payloadParameterNameTextBox.Text,
|
|
CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
|
|
|
|
if (config.ArchivePayload != archivePayloadCheckBox.Checked)
|
|
{
|
|
config.ArchivePayload = archivePayloadCheckBox.Checked;
|
|
flags |= (CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
|
|
}
|
|
|
|
flags |= UpdateDifferent(
|
|
ref config.PayloadArchivePath,
|
|
payloadArchivePathTextBox.Text,
|
|
CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
|
|
|
|
return flags;
|
|
}
|
|
|
|
private WriterCfg BuildTemporaryConfigFromUi()
|
|
{
|
|
WriterCfg tmpCfg = new WriterCfg(
|
|
string.IsNullOrWhiteSpace(nameTextBox.Text) ? "UniDataStorageWriter" : nameTextBox.Text,
|
|
config != null ? config.Factory : new Factory());
|
|
|
|
WriteMode mode;
|
|
if (!Enum.TryParse(writeModeComboBox.Text, true, out mode))
|
|
mode = WriteMode.Insert;
|
|
|
|
tmpCfg.WriteMode = mode;
|
|
tmpCfg.DataStorageType = (dataStorageTypeComboBox.Text ?? string.Empty).Trim();
|
|
tmpCfg.TechnologyType = (technologyTypeComboBox.Text ?? string.Empty).Trim();
|
|
tmpCfg.DataSource = dataSourceTextBox.Text;
|
|
tmpCfg.PayloadTemplatePath = (payloadTemplatePathTextBox.Text ?? string.Empty).Trim();
|
|
tmpCfg.PayloadParameterName = (payloadParameterNameTextBox.Text ?? string.Empty).Trim();
|
|
tmpCfg.ArchivePayload = archivePayloadCheckBox.Checked;
|
|
tmpCfg.PayloadArchivePath = (payloadArchivePathTextBox.Text ?? string.Empty).Trim();
|
|
|
|
tmpCfg.WriteTemplates = writeTemplatesListBox.Items
|
|
.Cast<string>()
|
|
.ToList();
|
|
|
|
string selectedTemplate = tmpCfg.GetTemplate(mode);
|
|
tmpCfg.QueryTemplate = selectedTemplate;
|
|
|
|
return tmpCfg;
|
|
}
|
|
|
|
private IDataStorageWriter CreateWriter(WriterCfg cfg)
|
|
{
|
|
if (cfg == null)
|
|
throw new ArgumentNullException(nameof(cfg));
|
|
|
|
string storageType = (cfg.DataStorageType ?? string.Empty).Trim();
|
|
string technologyType = (cfg.TechnologyType ?? string.Empty).Trim();
|
|
|
|
switch (storageType)
|
|
{
|
|
case StorageTypes.RestApi:
|
|
throw new NotSupportedException("REST API writer is not supported yet.");
|
|
|
|
case StorageTypes.LocalDatabase:
|
|
case StorageTypes.RemoteDatabase:
|
|
switch (technologyType)
|
|
{
|
|
case TechnologyTypes.MicrosoftSql:
|
|
return new DatabaseWriter(cfg);
|
|
|
|
case TechnologyTypes.MySqlMariaDb:
|
|
throw new NotSupportedException("MySQL (MariaDB) writer is not supported yet.");
|
|
|
|
case TechnologyTypes.SQLite:
|
|
throw new NotSupportedException("SQLite writer is not supported yet.");
|
|
|
|
default:
|
|
throw new NotSupportedException(
|
|
string.Format("Unsupported database technology type: '{0}'", cfg.TechnologyType));
|
|
}
|
|
|
|
case StorageTypes.LocalFile:
|
|
case StorageTypes.RemoteFile:
|
|
switch (technologyType)
|
|
{
|
|
case TechnologyTypes.Csv:
|
|
return new CsvWriter(cfg);
|
|
|
|
case TechnologyTypes.Xlsx:
|
|
return new XlsxWriter(cfg);
|
|
|
|
case TechnologyTypes.Json:
|
|
throw new NotSupportedException("JSON writer is not supported yet.");
|
|
|
|
case TechnologyTypes.Xml:
|
|
return new XmlFileWriter(cfg);
|
|
|
|
case TechnologyTypes.Xls:
|
|
throw new NotSupportedException("XLS writer is not supported yet.");
|
|
|
|
default:
|
|
throw new NotSupportedException(
|
|
string.Format("Unsupported file technology type: '{0}'", cfg.TechnologyType));
|
|
}
|
|
|
|
default:
|
|
throw new NotSupportedException(
|
|
string.Format("Unsupported DataStorageType: '{0}'", cfg.DataStorageType));
|
|
}
|
|
}
|
|
|
|
private void connectToDataSourceButton_Click(object sender, EventArgs e)
|
|
{
|
|
sourceTestResultTextBox.Clear();
|
|
|
|
try
|
|
{
|
|
WriterCfg cfg = BuildTemporaryConfigFromUi();
|
|
Writer component = new Writer(cfg);
|
|
WriterDiagnosticResult result = component.TestSource(true);
|
|
|
|
sourceTestResultTextBox.Text =
|
|
BuildDiagnosticHeader(cfg, "Source test") +
|
|
(result != null ? result.ToDisplayDiag() : "<null>");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
sourceTestResultTextBox.Text = BuildExceptionText("Data source test failed", ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes a diagnostic write using the values currently configured
|
|
/// in the control.
|
|
/// </summary>
|
|
private void writeDataByParamAndTemplateButton_Click(object sender, EventArgs e)
|
|
{
|
|
writeTestResultTextBox.Clear();
|
|
|
|
try
|
|
{
|
|
WriteMode mode;
|
|
if (!Enum.TryParse(writeModeComboBox.Text, true, out mode))
|
|
mode = WriteMode.Insert;
|
|
|
|
if ((mode == WriteMode.Insert || mode == WriteMode.Update) &&
|
|
batchWriteLines.Count == 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"At least one batch test item must be provided.");
|
|
}
|
|
|
|
WriterCfg cfg = BuildTemporaryConfigFromUi();
|
|
Writer component = new Writer(cfg);
|
|
|
|
DataWriteRequest request = BuildWriteRequest(batchWriteLines, mode);
|
|
|
|
WriterDiagnosticResult result = component.SetData(request);
|
|
|
|
writeTestResultTextBox.Text =
|
|
BuildDiagnosticHeader(cfg, "Write test") +
|
|
(result != null ? result.ToDisplayDiag() : "<null>");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
writeTestResultTextBox.Text = BuildExceptionText("WriteData test failed", ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds a write request from diagnostic input lines.
|
|
/// </summary>
|
|
private DataWriteRequest BuildWriteRequest(
|
|
IEnumerable<string> sourceLines,
|
|
WriteMode mode)
|
|
{
|
|
if (sourceLines == null)
|
|
throw new ArgumentNullException(
|
|
nameof(sourceLines));
|
|
|
|
DataWriteRequest request =
|
|
new DataWriteRequest();
|
|
|
|
request.Mode =
|
|
mode;
|
|
|
|
if (IsXmlFileUiTarget())
|
|
{
|
|
string xmlPayload =
|
|
sourceLines
|
|
.Where(
|
|
line =>
|
|
!string.IsNullOrWhiteSpace(
|
|
line))
|
|
.FirstOrDefault();
|
|
|
|
if (string.IsNullOrWhiteSpace(
|
|
xmlPayload))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"XML payload must be provided.");
|
|
}
|
|
|
|
ValidateXmlPayload(
|
|
xmlPayload);
|
|
|
|
request.Mode =
|
|
WriteMode.Insert;
|
|
|
|
request.Payload =
|
|
xmlPayload;
|
|
|
|
request.OutputFileName =
|
|
"UDSW_Test_" +
|
|
DateTime.Now.ToString(
|
|
"yyyyMMdd_HHmmss_fff") +
|
|
".xml";
|
|
|
|
return request;
|
|
}
|
|
|
|
foreach (string rawLine
|
|
in sourceLines)
|
|
{
|
|
string line =
|
|
rawLine == null
|
|
? string.Empty
|
|
: rawLine.Trim();
|
|
|
|
if (string.IsNullOrWhiteSpace(
|
|
line))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
switch (mode)
|
|
{
|
|
case WriteMode.Insert:
|
|
request.InsertItems.Add(
|
|
ParseInsertItem(
|
|
line));
|
|
break;
|
|
|
|
case WriteMode.Update:
|
|
request.UpdateItems.Add(
|
|
ParseUpdateItem(
|
|
line));
|
|
break;
|
|
|
|
case WriteMode.StoredProcedure:
|
|
request.StoredProcedureParameters.Add(
|
|
ParseStoredProcedureParameter(
|
|
line));
|
|
break;
|
|
}
|
|
}
|
|
|
|
return request;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses an insert parameter in the format <c>Column=Value</c>.
|
|
/// </summary>
|
|
/// <param name="text">
|
|
/// Text containing the column name and value.
|
|
/// </param>
|
|
/// <returns>
|
|
/// Parsed insert write item.
|
|
/// </returns>
|
|
/// <exception cref="InvalidOperationException">
|
|
/// Thrown when the input format is invalid.
|
|
/// </exception>
|
|
private InsertWriteItem ParseInsertItem(string text)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(text))
|
|
throw new InvalidOperationException("Insert item is empty.");
|
|
|
|
string[] parts = text
|
|
.Split(new[] { '=' }, 2, StringSplitOptions.None)
|
|
.Select(p => p.Trim())
|
|
.ToArray();
|
|
|
|
if (parts.Length != 2)
|
|
throw new InvalidOperationException(
|
|
"Insert format must be: Column=Value");
|
|
|
|
if (string.IsNullOrWhiteSpace(parts[0]))
|
|
throw new InvalidOperationException(
|
|
"Insert column name cannot be empty.");
|
|
|
|
if (string.IsNullOrWhiteSpace(parts[1]))
|
|
throw new InvalidOperationException(
|
|
"Insert value cannot be empty.");
|
|
|
|
return new InsertWriteItem
|
|
{
|
|
ColumnName = parts[0],
|
|
Value = parts[1]
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses an update parameter in the format
|
|
/// <c>WhereName=WhereValue;SetName=SetValue</c>.
|
|
/// </summary>
|
|
/// <param name="text">
|
|
/// Text containing the update condition and target value.
|
|
/// </param>
|
|
/// <returns>
|
|
/// Parsed update write item.
|
|
/// </returns>
|
|
/// <exception cref="InvalidOperationException">
|
|
/// Thrown when the input format is invalid.
|
|
/// </exception>
|
|
private UpdateWriteItem ParseUpdateItem(string text)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(text))
|
|
throw new InvalidOperationException("Update item is empty.");
|
|
|
|
string[] pairs = text
|
|
.Split(new[] { ';' }, StringSplitOptions.None)
|
|
.Select(p => p.Trim())
|
|
.ToArray();
|
|
|
|
if (pairs.Length != 2)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Update format must be: " +
|
|
"WhereName=WhereValue;SetName=SetValue");
|
|
}
|
|
|
|
string[] whereParts = pairs[0]
|
|
.Split(new[] { '=' }, 2, StringSplitOptions.None)
|
|
.Select(p => p.Trim())
|
|
.ToArray();
|
|
|
|
string[] setParts = pairs[1]
|
|
.Split(new[] { '=' }, 2, StringSplitOptions.None)
|
|
.Select(p => p.Trim())
|
|
.ToArray();
|
|
|
|
if (whereParts.Length != 2 || setParts.Length != 2)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Update format must be: " +
|
|
"WhereName=WhereValue;SetName=SetValue");
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(whereParts[0]))
|
|
throw new InvalidOperationException(
|
|
"WHERE column name cannot be empty.");
|
|
|
|
if (string.IsNullOrWhiteSpace(whereParts[1]))
|
|
throw new InvalidOperationException(
|
|
"WHERE value cannot be empty.");
|
|
|
|
if (string.IsNullOrWhiteSpace(setParts[0]))
|
|
throw new InvalidOperationException(
|
|
"SET column name cannot be empty.");
|
|
|
|
if (string.IsNullOrWhiteSpace(setParts[1]))
|
|
throw new InvalidOperationException(
|
|
"SET value cannot be empty.");
|
|
|
|
return new UpdateWriteItem
|
|
{
|
|
WhereParameterName = whereParts[0],
|
|
WhereValue = whereParts[1],
|
|
SetParameterName = setParts[0],
|
|
SetValue = setParts[1]
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses a stored procedure parameter definition entered in the
|
|
/// diagnostic UI.
|
|
/// </summary>
|
|
/// <param name="text">
|
|
/// Parameter definition in the format
|
|
/// <c>ParameterName|ParameterType=Value</c>.
|
|
/// </param>
|
|
/// <returns>
|
|
/// Parsed and strongly typed <see cref="StoredProcedureWriteParameter"/>.
|
|
/// </returns>
|
|
/// <exception cref="InvalidOperationException">
|
|
/// Thrown when the parameter definition is invalid or cannot be
|
|
/// converted to the requested type.
|
|
/// </exception>
|
|
/// <remarks>
|
|
/// Only the first <c>=</c> after the parameter type is used as the
|
|
/// separator. XML attributes containing additional <c>=</c> characters
|
|
/// are therefore preserved.
|
|
/// </remarks>
|
|
private StoredProcedureWriteParameter ParseStoredProcedureParameter(string text)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(text))
|
|
throw new InvalidOperationException("Stored procedure parameter is empty.");
|
|
|
|
int typeSeparatorIndex = text.IndexOf('|');
|
|
if (typeSeparatorIndex <= 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Stored procedure parameter format must be: ParameterName|ParameterType=Value");
|
|
}
|
|
|
|
int valueSeparatorIndex = text.IndexOf('=', typeSeparatorIndex + 1);
|
|
if (valueSeparatorIndex <= typeSeparatorIndex + 1)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Stored procedure parameter format must be: ParameterName|ParameterType=Value");
|
|
}
|
|
|
|
string parameterName = text.Substring(0, typeSeparatorIndex).Trim();
|
|
string parameterTypeText = text.Substring(
|
|
typeSeparatorIndex + 1,
|
|
valueSeparatorIndex - typeSeparatorIndex - 1).Trim();
|
|
string valueText = text.Substring(valueSeparatorIndex + 1);
|
|
|
|
if (string.IsNullOrWhiteSpace(parameterName))
|
|
throw new InvalidOperationException("Stored procedure parameter name cannot be empty.");
|
|
|
|
if (!parameterName.StartsWith("@", StringComparison.Ordinal))
|
|
parameterName = "@" + parameterName;
|
|
|
|
StoredProcedureParameterType parameterType;
|
|
if (!Enum.TryParse(parameterTypeText, true, out parameterType))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unsupported stored procedure parameter type: " + parameterTypeText);
|
|
}
|
|
|
|
return new StoredProcedureWriteParameter
|
|
{
|
|
ParameterName = parameterName,
|
|
ParameterType = parameterType,
|
|
Value = ConvertStoredProcedureValue(valueText, parameterType)
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a diagnostic text value to the .NET value corresponding
|
|
/// to the selected stored procedure parameter type.
|
|
/// </summary>
|
|
private object ConvertStoredProcedureValue(
|
|
string value,
|
|
StoredProcedureParameterType parameterType)
|
|
{
|
|
switch (parameterType)
|
|
{
|
|
case StoredProcedureParameterType.String:
|
|
case StoredProcedureParameterType.Xml:
|
|
return value;
|
|
|
|
case StoredProcedureParameterType.Int32:
|
|
{
|
|
int parsedValue;
|
|
if (!int.TryParse(
|
|
value,
|
|
NumberStyles.Integer,
|
|
CultureInfo.InvariantCulture,
|
|
out parsedValue))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Value is not a valid Int32: " + value);
|
|
}
|
|
|
|
return parsedValue;
|
|
}
|
|
|
|
case StoredProcedureParameterType.Int64:
|
|
{
|
|
long parsedValue;
|
|
if (!long.TryParse(
|
|
value,
|
|
NumberStyles.Integer,
|
|
CultureInfo.InvariantCulture,
|
|
out parsedValue))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Value is not a valid Int64: " + value);
|
|
}
|
|
|
|
return parsedValue;
|
|
}
|
|
|
|
case StoredProcedureParameterType.Decimal:
|
|
{
|
|
decimal parsedValue;
|
|
if (!decimal.TryParse(
|
|
value,
|
|
NumberStyles.Number,
|
|
CultureInfo.InvariantCulture,
|
|
out parsedValue))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Value is not a valid Decimal: " + value);
|
|
}
|
|
|
|
return parsedValue;
|
|
}
|
|
|
|
case StoredProcedureParameterType.Boolean:
|
|
{
|
|
bool parsedValue;
|
|
if (bool.TryParse(value, out parsedValue))
|
|
return parsedValue;
|
|
|
|
if (value == "1")
|
|
return true;
|
|
|
|
if (value == "0")
|
|
return false;
|
|
|
|
throw new InvalidOperationException(
|
|
"Value is not a valid Boolean: " + value);
|
|
}
|
|
|
|
case StoredProcedureParameterType.DateTime:
|
|
{
|
|
DateTime parsedValue;
|
|
if (!DateTime.TryParse(
|
|
value,
|
|
CultureInfo.InvariantCulture,
|
|
DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.RoundtripKind,
|
|
out parsedValue))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Value is not a valid DateTime: " + value);
|
|
}
|
|
|
|
return parsedValue;
|
|
}
|
|
|
|
default:
|
|
throw new InvalidOperationException(
|
|
"Unsupported stored procedure parameter type: " + parameterType);
|
|
}
|
|
}
|
|
|
|
private void buttonAddParam_Click(
|
|
object sender,
|
|
EventArgs e)
|
|
{
|
|
string text =
|
|
writeParamValueTextBox.Text == null
|
|
? string.Empty
|
|
: writeParamValueTextBox.Text.Trim();
|
|
|
|
if (string.IsNullOrWhiteSpace(
|
|
text))
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (IsXmlFileUiTarget())
|
|
{
|
|
ValidateXmlPayload(
|
|
text);
|
|
|
|
//
|
|
// One diagnostic XML-file request represents one complete
|
|
// document, therefore keep only one payload row.
|
|
//
|
|
batchWriteLines.Clear();
|
|
batchWriteLines.Add(
|
|
text);
|
|
|
|
writeParamValueTextBox.Clear();
|
|
|
|
RefreshWriteParamsListBox();
|
|
|
|
return;
|
|
}
|
|
|
|
WriteMode mode;
|
|
|
|
if (!Enum.TryParse(
|
|
writeModeComboBox.Text,
|
|
true,
|
|
out mode))
|
|
{
|
|
mode =
|
|
WriteMode.Insert;
|
|
}
|
|
|
|
switch (mode)
|
|
{
|
|
case WriteMode.Insert:
|
|
ParseInsertItem(
|
|
text);
|
|
break;
|
|
|
|
case WriteMode.Update:
|
|
ParseUpdateItem(
|
|
text);
|
|
break;
|
|
|
|
case WriteMode.StoredProcedure:
|
|
ParseStoredProcedureParameter(
|
|
text);
|
|
break;
|
|
}
|
|
|
|
batchWriteLines.Add(
|
|
text);
|
|
|
|
writeParamValueTextBox.Clear();
|
|
|
|
RefreshWriteParamsListBox();
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
writeTestResultTextBox.Text =
|
|
"Invalid write parameter:" +
|
|
Environment.NewLine +
|
|
ex.Message;
|
|
|
|
writeParamValueTextBox.Focus();
|
|
writeParamValueTextBox.SelectAll();
|
|
}
|
|
}
|
|
|
|
private void buttonRemoveParam_Click(object sender, EventArgs e)
|
|
{
|
|
int index = listBoxWriteParams.SelectedIndex;
|
|
|
|
if (index < 0 || index >= batchWriteLines.Count)
|
|
return;
|
|
|
|
batchWriteLines.RemoveAt(index);
|
|
RefreshWriteParamsListBox();
|
|
}
|
|
|
|
private void RefreshWriteParamsListBox()
|
|
{
|
|
listBoxWriteParams.Items.Clear();
|
|
|
|
for (int i = 0; i < batchWriteLines.Count; i++)
|
|
{
|
|
listBoxWriteParams.Items.Add(
|
|
string.Format(
|
|
"{0}. {1}",
|
|
i + 1,
|
|
BuildWriteParameterDisplayText(
|
|
batchWriteLines[i])));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a compact display text for one diagnostic write parameter.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// XML stored-procedure parameters are intentionally abbreviated in the
|
|
/// list because the complete XML can be inspected by double-clicking
|
|
/// the row.
|
|
/// </remarks>
|
|
private string BuildWriteParameterDisplayText(
|
|
string rawLine)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(
|
|
rawLine))
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
if (IsXmlFileUiTarget())
|
|
{
|
|
string rootName =
|
|
TryGetXmlRootName(
|
|
rawLine);
|
|
|
|
return string.Format(
|
|
"XML payload | <{0} ...> [double-click to view]",
|
|
string.IsNullOrWhiteSpace(
|
|
rootName)
|
|
? "XML"
|
|
: rootName);
|
|
}
|
|
|
|
WriteMode mode;
|
|
|
|
if (!Enum.TryParse(
|
|
writeModeComboBox.Text,
|
|
true,
|
|
out mode) ||
|
|
mode !=
|
|
WriteMode.StoredProcedure)
|
|
{
|
|
return rawLine;
|
|
}
|
|
|
|
try
|
|
{
|
|
StoredProcedureWriteParameter parameter =
|
|
ParseStoredProcedureParameter(
|
|
rawLine);
|
|
|
|
if (parameter.ParameterType !=
|
|
StoredProcedureParameterType.Xml)
|
|
{
|
|
return rawLine;
|
|
}
|
|
|
|
string xml =
|
|
Convert.ToString(
|
|
parameter.Value);
|
|
|
|
string rootName =
|
|
TryGetXmlRootName(
|
|
xml);
|
|
|
|
return string.Format(
|
|
"{0} | Xml | <{1} ...> [double-click to view]",
|
|
parameter.ParameterName,
|
|
string.IsNullOrWhiteSpace(
|
|
rootName)
|
|
? "XML"
|
|
: rootName);
|
|
}
|
|
catch
|
|
{
|
|
return rawLine;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the root element name of an XML string when possible.
|
|
/// </summary>
|
|
private string TryGetXmlRootName(
|
|
string xml)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(xml))
|
|
return null;
|
|
|
|
try
|
|
{
|
|
System.Xml.Linq.XDocument document =
|
|
System.Xml.Linq.XDocument.Parse(
|
|
xml,
|
|
System.Xml.Linq.LoadOptions.PreserveWhitespace);
|
|
|
|
return document.Root != null
|
|
? document.Root.Name.LocalName
|
|
: null;
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens a read-only XML payload viewer for an XML stored-procedure parameter.
|
|
/// </summary>
|
|
private void listBoxWriteParams_DoubleClick(
|
|
object sender,
|
|
EventArgs e)
|
|
{
|
|
int index =
|
|
listBoxWriteParams.SelectedIndex;
|
|
|
|
if (index < 0 ||
|
|
index >=
|
|
batchWriteLines.Count)
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (IsXmlFileUiTarget())
|
|
{
|
|
using (XmlPayloadViewerDlg dlg =
|
|
new XmlPayloadViewerDlg())
|
|
{
|
|
dlg.ParameterName =
|
|
"XML file payload";
|
|
|
|
dlg.XmlPayload =
|
|
batchWriteLines[index];
|
|
|
|
dlg.ShowDialog(
|
|
this);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
WriteMode mode;
|
|
|
|
if (!Enum.TryParse(
|
|
writeModeComboBox.Text,
|
|
true,
|
|
out mode) ||
|
|
mode !=
|
|
WriteMode.StoredProcedure)
|
|
{
|
|
return;
|
|
}
|
|
|
|
StoredProcedureWriteParameter parameter =
|
|
ParseStoredProcedureParameter(
|
|
batchWriteLines[index]);
|
|
|
|
if (parameter.ParameterType !=
|
|
StoredProcedureParameterType.Xml)
|
|
{
|
|
return;
|
|
}
|
|
|
|
string xml =
|
|
Convert.ToString(
|
|
parameter.Value);
|
|
|
|
using (XmlPayloadViewerDlg dlg =
|
|
new XmlPayloadViewerDlg())
|
|
{
|
|
dlg.ParameterName =
|
|
parameter.ParameterName;
|
|
|
|
dlg.XmlPayload =
|
|
xml;
|
|
|
|
dlg.ShowDialog(
|
|
this);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MessageBox.Show(
|
|
this,
|
|
ex.Message,
|
|
"XML payload viewer",
|
|
MessageBoxButtons.OK,
|
|
MessageBoxIcon.Error);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the diagnostic input hint according to the selected write mode.
|
|
/// </summary>
|
|
private void UpdateInputHint()
|
|
{
|
|
if (IsXmlFileUiTarget())
|
|
{
|
|
labelParamValue.Text =
|
|
"XML payload: paste complete XML document";
|
|
|
|
if (string.IsNullOrWhiteSpace(
|
|
writeParamValueTextBox.Text) ||
|
|
writeParamValueTextBox.Text ==
|
|
"Column=Value" ||
|
|
writeParamValueTextBox.Text.StartsWith(
|
|
"WhereName=",
|
|
StringComparison.Ordinal) ||
|
|
writeParamValueTextBox.Text.StartsWith(
|
|
"@DashboardResults|",
|
|
StringComparison.Ordinal))
|
|
{
|
|
writeParamValueTextBox.Text =
|
|
"<BATCH />";
|
|
}
|
|
|
|
ApplyPayloadConfiguration();
|
|
|
|
return;
|
|
}
|
|
|
|
WriteMode mode;
|
|
|
|
if (!Enum.TryParse(
|
|
writeModeComboBox.Text,
|
|
true,
|
|
out mode))
|
|
{
|
|
mode =
|
|
WriteMode.Insert;
|
|
}
|
|
|
|
switch (mode)
|
|
{
|
|
case WriteMode.Insert:
|
|
labelParamValue.Text =
|
|
"Insert: Column=Value";
|
|
|
|
writeParamValueTextBox.Text =
|
|
"Column=Value";
|
|
break;
|
|
|
|
case WriteMode.Update:
|
|
labelParamValue.Text =
|
|
"Update: WhereName=WhereValue;SetName=SetValue";
|
|
|
|
writeParamValueTextBox.Text =
|
|
"WhereName=WhereValue;SetName=SetValue";
|
|
break;
|
|
|
|
case WriteMode.StoredProcedure:
|
|
labelParamValue.Text =
|
|
"Stored procedure: Parameter|Type=Value";
|
|
|
|
writeParamValueTextBox.Text =
|
|
"@DashboardResults|Xml=<BATCH />";
|
|
break;
|
|
|
|
default:
|
|
labelParamValue.Text =
|
|
"Parameter";
|
|
|
|
writeParamValueTextBox.Clear();
|
|
break;
|
|
}
|
|
|
|
ApplyPayloadConfiguration();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns whether the UI currently represents a direct XML-file target.
|
|
/// </summary>
|
|
private bool IsXmlFileUiTarget()
|
|
{
|
|
string storageType =
|
|
(dataStorageTypeComboBox.Text ??
|
|
string.Empty)
|
|
.Trim();
|
|
|
|
string technologyType =
|
|
(technologyTypeComboBox.Text ??
|
|
string.Empty)
|
|
.Trim();
|
|
|
|
return
|
|
(storageType ==
|
|
StorageTypes.LocalFile ||
|
|
storageType ==
|
|
StorageTypes.RemoteFile) &&
|
|
technologyType ==
|
|
TechnologyTypes.Xml;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates one complete XML diagnostic payload.
|
|
/// </summary>
|
|
private void ValidateXmlPayload(
|
|
string xml)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(
|
|
xml))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"XML payload is empty.");
|
|
}
|
|
|
|
try
|
|
{
|
|
System.Xml.Linq.XDocument.Parse(
|
|
xml,
|
|
System.Xml.Linq.LoadOptions.PreserveWhitespace);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"XML payload is invalid: " +
|
|
ex.Message,
|
|
ex);
|
|
}
|
|
}
|
|
|
|
private string BuildDiagnosticHeader(WriterCfg cfg, string testName)
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
sb.AppendLine("=== " + testName + " ===");
|
|
sb.AppendLine("Storage type: " + StorageTypeToDisplayName(cfg.DataStorageType));
|
|
sb.AppendLine("Technology type: " + Safe(cfg.TechnologyType));
|
|
sb.AppendLine("Data source: " + Safe(cfg.DataSource));
|
|
sb.AppendLine("Write mode: " + Safe(writeModeComboBox.Text));
|
|
|
|
WriteMode mode;
|
|
if (!Enum.TryParse(writeModeComboBox.Text, true, out mode))
|
|
mode = WriteMode.Insert;
|
|
|
|
string template = cfg.GetTemplate(mode);
|
|
|
|
sb.AppendLine("Write template: " + Safe(template));
|
|
sb.AppendLine("Payload template: " + Safe(cfg.PayloadTemplatePath));
|
|
sb.AppendLine("Payload parameter: " + Safe(cfg.PayloadParameterName));
|
|
sb.AppendLine("Archive payload: " + cfg.ArchivePayload);
|
|
if (cfg.ArchivePayload)
|
|
sb.AppendLine("Payload archive path: " + Safe(cfg.PayloadArchivePath));
|
|
sb.AppendLine();
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
private string BuildExceptionText(string title, Exception ex)
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
sb.AppendLine(title + ":");
|
|
sb.AppendLine(ex.Message);
|
|
|
|
Exception inner = ex.InnerException;
|
|
while (inner != null)
|
|
{
|
|
sb.AppendLine();
|
|
sb.AppendLine("Inner exception:");
|
|
sb.AppendLine(inner.Message);
|
|
inner = inner.InnerException;
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
public static string Safe(string text)
|
|
{
|
|
return string.IsNullOrWhiteSpace(text) ? "<empty>" : text;
|
|
}
|
|
|
|
private string StorageTypeToDisplayName(string storageType)
|
|
{
|
|
switch ((storageType ?? string.Empty).Trim())
|
|
{
|
|
case StorageTypes.RestApi:
|
|
return "REST API";
|
|
|
|
case StorageTypes.LocalDatabase:
|
|
return "Local database";
|
|
|
|
case StorageTypes.RemoteDatabase:
|
|
return "Remote database";
|
|
|
|
case StorageTypes.LocalFile:
|
|
return "Local file";
|
|
|
|
case StorageTypes.RemoteFile:
|
|
return "Remote file";
|
|
|
|
default:
|
|
return Safe(storageType);
|
|
}
|
|
}
|
|
|
|
private void dataStorageTypeComboBox_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
UpdateTechnologyTypeUi();
|
|
}
|
|
|
|
private void UpdateTechnologyTypeUi()
|
|
{
|
|
string previousTechnology =
|
|
(technologyTypeComboBox.Text ?? string.Empty).Trim();
|
|
|
|
string storageType =
|
|
(dataStorageTypeComboBox.Text ?? string.Empty).Trim();
|
|
|
|
technologyTypeComboBox.Items.Clear();
|
|
|
|
switch (storageType)
|
|
{
|
|
case StorageTypes.LocalDatabase:
|
|
case StorageTypes.RemoteDatabase:
|
|
technologyTypeLabel.Text = "Database type:";
|
|
|
|
technologyTypeComboBox.Items.Add(
|
|
TechnologyTypes.MicrosoftSql);
|
|
|
|
technologyTypeComboBox.Items.Add(
|
|
TechnologyTypes.MySqlMariaDb);
|
|
|
|
technologyTypeComboBox.Items.Add(
|
|
TechnologyTypes.SQLite);
|
|
break;
|
|
|
|
case StorageTypes.LocalFile:
|
|
case StorageTypes.RemoteFile:
|
|
technologyTypeLabel.Text = "File type:";
|
|
|
|
technologyTypeComboBox.Items.Add(
|
|
TechnologyTypes.Csv);
|
|
|
|
technologyTypeComboBox.Items.Add(
|
|
TechnologyTypes.Xlsx);
|
|
|
|
technologyTypeComboBox.Items.Add(
|
|
TechnologyTypes.Xls);
|
|
|
|
technologyTypeComboBox.Items.Add(
|
|
TechnologyTypes.Json);
|
|
|
|
technologyTypeComboBox.Items.Add(
|
|
TechnologyTypes.Xml);
|
|
break;
|
|
|
|
default:
|
|
technologyTypeLabel.Text = "Technology type:";
|
|
break;
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(previousTechnology) &&
|
|
technologyTypeComboBox.Items.Contains(previousTechnology))
|
|
{
|
|
technologyTypeComboBox.SelectedItem =
|
|
previousTechnology;
|
|
}
|
|
else if (technologyTypeComboBox.Items.Count > 0)
|
|
{
|
|
technologyTypeComboBox.SelectedIndex = 0;
|
|
}
|
|
|
|
ApplyTechnologyConfiguration(true);
|
|
}
|
|
|
|
private void writeModeComboBox_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
UpdateInputHint();
|
|
}
|
|
|
|
private void RefreshTemplatesListBox()
|
|
{
|
|
writeTemplatesListBox.Items.Clear();
|
|
|
|
if (config == null || config.WriteTemplates == null)
|
|
return;
|
|
|
|
foreach (string item in config.WriteTemplates)
|
|
writeTemplatesListBox.Items.Add(item);
|
|
}
|
|
|
|
private void writeTemplatesListBox_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
if (writeTemplatesListBox.SelectedItem == null)
|
|
{
|
|
templateEditTextBox.Clear();
|
|
return;
|
|
}
|
|
|
|
templateEditTextBox.Text = writeTemplatesListBox.SelectedItem.ToString();
|
|
}
|
|
|
|
private void technologyTypeComboBox_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
ApplyTechnologyConfiguration(true);
|
|
}
|
|
|
|
private void buttonAddTemplate_Click(object sender, EventArgs e)
|
|
{
|
|
string text = (templateEditTextBox.Text ?? string.Empty).Trim();
|
|
|
|
if (string.IsNullOrWhiteSpace(text))
|
|
return;
|
|
|
|
writeTemplatesListBox.Items.Add(text);
|
|
|
|
templateEditTextBox.Clear();
|
|
}
|
|
|
|
private void buttonUpdateTemplate_Click(object sender, EventArgs e)
|
|
{
|
|
int index = writeTemplatesListBox.SelectedIndex;
|
|
if (index < 0)
|
|
return;
|
|
|
|
string text = (templateEditTextBox.Text ?? string.Empty).Trim();
|
|
if (string.IsNullOrWhiteSpace(text))
|
|
return;
|
|
|
|
// Update only the UI list. Configuration will be synchronized in UpdateCfg().
|
|
writeTemplatesListBox.Items[index] = text;
|
|
writeTemplatesListBox.SelectedIndex = index;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes selected template from UI list.
|
|
/// Configuration is updated later in UpdateCfg().
|
|
/// </summary>
|
|
private void buttonRemoveTemplate_Click(object sender, EventArgs e)
|
|
{
|
|
// Get selected index from list
|
|
int index = writeTemplatesListBox.SelectedIndex;
|
|
|
|
// Validate selection
|
|
if (index < 0)
|
|
return;
|
|
|
|
// Remove item only from UI (NOT from config!)
|
|
writeTemplatesListBox.Items.RemoveAt(index);
|
|
|
|
// Clear edit textbox
|
|
templateEditTextBox.Clear();
|
|
|
|
// Optional: select next valid item (better UX)
|
|
if (writeTemplatesListBox.Items.Count > 0)
|
|
{
|
|
int newIndex = Math.Min(index, writeTemplatesListBox.Items.Count - 1);
|
|
writeTemplatesListBox.SelectedIndex = newIndex;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enables or disables payload-specific configuration controls.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Payload templates are currently exposed for Microsoft SQL stored
|
|
/// procedure mode. The fields remain part of the configuration even
|
|
/// when the controls are disabled.
|
|
/// </remarks>
|
|
private void ApplyPayloadConfiguration()
|
|
{
|
|
WriteMode mode;
|
|
|
|
if (!Enum.TryParse(
|
|
writeModeComboBox.Text,
|
|
true,
|
|
out mode))
|
|
{
|
|
mode =
|
|
WriteMode.Insert;
|
|
}
|
|
|
|
string storageType =
|
|
(dataStorageTypeComboBox.Text ??
|
|
string.Empty)
|
|
.Trim();
|
|
|
|
string technologyType =
|
|
(technologyTypeComboBox.Text ??
|
|
string.Empty)
|
|
.Trim();
|
|
|
|
bool storedProcedurePayload =
|
|
(storageType ==
|
|
StorageTypes.LocalDatabase ||
|
|
storageType ==
|
|
StorageTypes.RemoteDatabase) &&
|
|
technologyType ==
|
|
TechnologyTypes.MicrosoftSql &&
|
|
mode ==
|
|
WriteMode.StoredProcedure;
|
|
|
|
bool xmlFilePayload =
|
|
(storageType ==
|
|
StorageTypes.LocalFile ||
|
|
storageType ==
|
|
StorageTypes.RemoteFile) &&
|
|
technologyType ==
|
|
TechnologyTypes.Xml;
|
|
|
|
bool payloadEditingEnabled =
|
|
isUnlocked &&
|
|
(storedProcedurePayload ||
|
|
xmlFilePayload);
|
|
|
|
groupBox6.Text =
|
|
xmlFilePayload
|
|
? "XML payload setting"
|
|
: "Payload template setting";
|
|
|
|
payloadTemplatePathLabel.Text =
|
|
xmlFilePayload
|
|
? "Payload reference:"
|
|
: "Payload template:";
|
|
|
|
payloadTemplatePathTextBox.Enabled =
|
|
payloadEditingEnabled;
|
|
|
|
browsePayloadTemplateButton.Enabled =
|
|
payloadEditingEnabled;
|
|
|
|
//
|
|
// A direct XML-file target does not have a stored-procedure
|
|
// parameter and does not need a second archive copy. The generated
|
|
// XML file itself is the final storage output.
|
|
//
|
|
payloadParameterNameLabel.Visible =
|
|
!xmlFilePayload;
|
|
|
|
payloadParameterNameTextBox.Visible =
|
|
!xmlFilePayload;
|
|
|
|
archivePayloadCheckBox.Visible =
|
|
!xmlFilePayload;
|
|
|
|
payloadArchivePathLabel.Visible =
|
|
!xmlFilePayload;
|
|
|
|
payloadArchivePathTextBox.Visible =
|
|
!xmlFilePayload;
|
|
|
|
browsePayloadArchiveButton.Visible =
|
|
!xmlFilePayload;
|
|
|
|
payloadParameterNameTextBox.Enabled =
|
|
isUnlocked &&
|
|
storedProcedurePayload;
|
|
|
|
archivePayloadCheckBox.Enabled =
|
|
isUnlocked &&
|
|
storedProcedurePayload;
|
|
|
|
bool archivePathEnabled =
|
|
isUnlocked &&
|
|
storedProcedurePayload &&
|
|
archivePayloadCheckBox.Checked;
|
|
|
|
payloadArchivePathTextBox.Enabled =
|
|
archivePathEnabled;
|
|
|
|
browsePayloadArchiveButton.Enabled =
|
|
archivePathEnabled;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens a file browser used to select an external XML payload template.
|
|
/// </summary>
|
|
private void browsePayloadTemplateButton_Click(object sender, EventArgs e)
|
|
{
|
|
using (OpenFileDialog dialog = new OpenFileDialog())
|
|
{
|
|
dialog.Title = "Select XML payload template";
|
|
dialog.Filter =
|
|
"XML files (*.xml)|*.xml|All files (*.*)|*.*";
|
|
dialog.CheckFileExists = true;
|
|
dialog.Multiselect = false;
|
|
|
|
string currentPath =
|
|
(payloadTemplatePathTextBox.Text ?? string.Empty).Trim();
|
|
|
|
if (!string.IsNullOrWhiteSpace(currentPath))
|
|
{
|
|
try
|
|
{
|
|
string directory = Path.GetDirectoryName(currentPath);
|
|
if (!string.IsNullOrWhiteSpace(directory) &&
|
|
Directory.Exists(directory))
|
|
{
|
|
dialog.InitialDirectory = directory;
|
|
}
|
|
|
|
string fileName = Path.GetFileName(currentPath);
|
|
if (!string.IsNullOrWhiteSpace(fileName))
|
|
dialog.FileName = fileName;
|
|
}
|
|
catch
|
|
{
|
|
// Ignore malformed current path and open the default folder.
|
|
}
|
|
}
|
|
|
|
if (dialog.ShowDialog(this) == DialogResult.OK)
|
|
payloadTemplatePathTextBox.Text = dialog.FileName;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens a folder browser used to select the payload archive directory.
|
|
/// </summary>
|
|
private void browsePayloadArchiveButton_Click(object sender, EventArgs e)
|
|
{
|
|
using (FolderBrowserDialog dialog = new FolderBrowserDialog())
|
|
{
|
|
dialog.Description =
|
|
"Select directory for generated payload archive files.";
|
|
dialog.ShowNewFolderButton = true;
|
|
|
|
string currentPath =
|
|
(payloadArchivePathTextBox.Text ?? string.Empty).Trim();
|
|
|
|
if (!string.IsNullOrWhiteSpace(currentPath) &&
|
|
Directory.Exists(currentPath))
|
|
{
|
|
dialog.SelectedPath = currentPath;
|
|
}
|
|
|
|
if (dialog.ShowDialog(this) == DialogResult.OK)
|
|
payloadArchivePathTextBox.Text = dialog.SelectedPath;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates archive path control availability when payload archiving is
|
|
/// enabled or disabled.
|
|
/// </summary>
|
|
private void archivePayloadCheckBox_CheckedChanged(object sender, EventArgs e)
|
|
{
|
|
ApplyPayloadConfiguration();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the data source hint for the selected technology.
|
|
/// </summary>
|
|
/// <param name="technologyType">
|
|
/// Selected technology type.
|
|
/// </param>
|
|
/// <returns>
|
|
/// Data source input hint.
|
|
/// </returns>
|
|
private string GetDataSourceHint(string technologyType)
|
|
{
|
|
switch (technologyType)
|
|
{
|
|
case TechnologyTypes.Csv:
|
|
return "Insert the .csv file path";
|
|
|
|
case TechnologyTypes.Xlsx:
|
|
return "Insert the .xlsx file path";
|
|
|
|
case TechnologyTypes.Xls:
|
|
return "Insert the .xls file path";
|
|
|
|
case TechnologyTypes.Xml:
|
|
return "Insert output directory for generated .xml payload files";
|
|
|
|
case TechnologyTypes.MicrosoftSql:
|
|
case TechnologyTypes.MySqlMariaDb:
|
|
case TechnologyTypes.SQLite:
|
|
return "Insert database connection string";
|
|
|
|
default:
|
|
return "Insert data storage source";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the placeholder text displayed by the data source textbox.
|
|
/// </summary>
|
|
/// <param name="hint">
|
|
/// Placeholder text.
|
|
/// </param>
|
|
private void SetDataSourceHint(string hint)
|
|
{
|
|
if (!dataSourceTextBox.IsHandleCreated)
|
|
return;
|
|
|
|
SendMessage(
|
|
dataSourceTextBox.Handle,
|
|
EmSetCueBanner,
|
|
new IntPtr(1),
|
|
hint ?? string.Empty);
|
|
}
|
|
}
|
|
} |