/* ========================================================================
* Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
*
* OPC Foundation MIT License 1.00
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* The complete license agreement can be found here:
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/
using System;
using System.Collections.Generic;
using System.IO;
using Opc.Ua.Schema.Model;
namespace Opc.Ua.SourceGeneration
{
///
/// Generates endpoint code based on a UA Type Dictionary.
///
internal sealed class EndpointsGenerator : IGenerator
{
///
/// Initializes a new instance of the class.
///
public EndpointsGenerator(IGeneratorContext context)
{
m_context = context ?? throw new ArgumentNullException(nameof(context));
}
///
public IEnumerable Emit()
{
List serviceSets =
[
new ServiceSet(
"Session",
ServiceCategory.Discovery,
ServiceCategory.Session,
ServiceCategory.Attribute,
ServiceCategory.View,
ServiceCategory.Method,
ServiceCategory.MonitoredItem,
ServiceCategory.Subscription,
ServiceCategory.NodeManagement,
ServiceCategory.Query,
ServiceCategory.Test),
new ServiceSet(
"Discovery",
ServiceCategory.Discovery,
ServiceCategory.Registration)
];
string fileName = Path.Combine(
m_context.OutputFolder,
CoreUtils.Format("{0}.Endpoints.g.cs", Constants.CoreNamespacePrefix));
using TextWriter writer = m_context.FileSystem.CreateTextWriter(fileName);
using var templateWriter = new TemplateWriter(writer);
var template = new Template(templateWriter, EndpointsTemplates.File);
template.AddReplacement(Tokens.Prefix, Constants.CoreNamespacePrefix);
template.AddReplacement(Tokens.Namespace, Constants.CoreNamespace);
template.AddReplacement(
Tokens.ServiceSets,
EndpointsTemplates.ServiceSet,
serviceSets,
WriteTemplate_EndpointServiceSet);
template.Render();
return [fileName.AsTextFileResource()];
}
///
/// Writes the endpoint service set.
///
private bool WriteTemplate_EndpointServiceSet(IWriteContext context)
{
if (context.Target is not ServiceSet serviceSet)
{
return false;
}
// get datatypes.
Service[] datatypes = m_context.ModelDesign.GetListOfServices(serviceSet.Categories);
if (datatypes.Length == 0)
{
return false;
}
context.Template.AddReplacement(Tokens.ServiceSet, serviceSet.Name);
context.Template.AddReplacement(
Tokens.MethodList,
EndpointsTemplates.Method,
datatypes,
WriteTemplate_EndpointMethod);
context.Template.AddReplacement(
Tokens.AddKnownType,
datatypes,
LoadTemplate_KnownType);
return context.Template.Render();
}
///
/// Writes an endpoint method.
///
private bool WriteTemplate_EndpointMethod(IWriteContext context)
{
if (context.Target is not Service serviceType)
{
return false;
}
context.Template.AddReplacement(Tokens.Name, serviceType.Name);
context.Template.AddReplacement(
Tokens.InvokeServiceAsync,
[serviceType],
LoadTemplate_InvokeServiceAsyncParameters);
return context.Template.Render();
}
///
/// Writes an asynchronous method declaration.
///
private TemplateString LoadTemplate_InvokeServiceAsyncParameters(ILoadContext context)
{
if (context.Target is not Service serviceType)
{
return null;
}
// write method declaration.
context.Out.WriteLine(
"response = await ServerInstance.{0}Async(",
serviceType.Name);
context.Out.Write(" secureChannelContext");
if (serviceType.Request != null || serviceType.Request.Fields.Length > 0)
{
foreach (Parameter field in serviceType.Request.Fields)
{
context.Out.WriteLine(",");
context.Out.Write(" request.{0}", field.Name);
}
}
context.Out.WriteLine(",");
context.Out.WriteLine(" requestLifetime).ConfigureAwait(false);");
return null;
}
///
/// Writes a synchronous method declaration for known types.
///
private TemplateString LoadTemplate_KnownType(ILoadContext context)
{
if (context.Target is not Service serviceType)
{
return null;
}
// write method declaration.
context.Out.WriteLine(
"SupportedServices.Add(DataTypeIds.{0}Request, new ServiceDefinition(typeof({0}Request), new InvokeService({0}Async)));",
serviceType.Name);
context.Out.WriteLine();
return null;
}
///
/// A set of services that are grouped into a single interface.
///
private sealed record class ServiceSet(string Name, params ServiceCategory[] Categories);
private readonly IGeneratorContext m_context;
}
}