Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic client callback registration #204

Merged
merged 3 commits into from
Nov 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion YaNco.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Abap/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Dbosoft/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Dbosoft/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=sysid/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
2 changes: 2 additions & 0 deletions src/YaNco.Abstractions/IConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public interface IConnection : IDisposable
EitherAsync<RfcErrorInfo, IFunction> CreateFunction(string name);
EitherAsync<RfcErrorInfo, Unit> InvokeFunction(IFunction function);
EitherAsync<RfcErrorInfo, Unit> InvokeFunction(IFunction function, CancellationToken cancellationToken);

[Obsolete("Use method WithStartProgramCallback of ConnectionBuilder instead. This method will be removed in next major release.")]
EitherAsync<RfcErrorInfo, Unit> AllowStartOfPrograms(StartProgramDelegate callback);
EitherAsync<RfcErrorInfo, Unit> Cancel();

Expand Down
1 change: 1 addition & 0 deletions src/YaNco.Abstractions/IDataContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ public interface IDataContainer : IDisposable
Either<RfcErrorInfo, byte[]> GetFieldBytes(string name);
Either<RfcErrorInfo, IStructure> GetStructure(string name);
Either<RfcErrorInfo, ITable> GetTable(string name);
Either<RfcErrorInfo, ITypeDescriptionHandle> GetTypeDescription();
}
}
18 changes: 18 additions & 0 deletions src/YaNco.Abstractions/IFunctionBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using LanguageExt;

namespace Dbosoft.YaNco
{
public interface IFunctionBuilder
{
IFunctionBuilder AddParameter(RfcParameterDescription parameter);
IFunctionBuilder AddChar(string name, RfcDirection direction, uint length, bool optional = true, string defaultValue = null);
IFunctionBuilder AddInt(string name, RfcDirection direction, bool optional = true, int defaultValue = 0);
IFunctionBuilder AddLong(string name, RfcDirection direction, bool optional = true, long defaultValue = 0);
IFunctionBuilder AddString(string name, RfcDirection direction, bool optional = true, uint length = 0, string defaultValue = null);
IFunctionBuilder AddStructure(string name, RfcDirection direction, ITypeDescriptionHandle typeHandle, bool optional = true);
IFunctionBuilder AddStructure(string name, RfcDirection direction, IStructure structure, bool optional = true);
IFunctionBuilder AddTable(string name, RfcDirection direction, ITable table, bool optional = true);
IFunctionBuilder AddTable(string name, RfcDirection direction, ITypeDescriptionHandle typeHandle, bool optional = true);
Either<RfcErrorInfo, IFunctionDescriptionHandle> Build();
}
}
6 changes: 6 additions & 0 deletions src/YaNco.Abstractions/IRfcHandle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Dbosoft.YaNco
{
public interface IRfcHandle
{
}
}
9 changes: 9 additions & 0 deletions src/YaNco.Abstractions/IRfcRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Dbosoft.YaNco
{
public interface IRfcRuntime
{
[Obsolete("Use method AllowStartOfPrograms of ConnectionBuilder. This method will be removed in next major release.")]
Either<RfcErrorInfo, Unit> AllowStartOfPrograms(IConnectionHandle connectionHandle, StartProgramDelegate callback);
Either<RfcErrorInfo, IStructureHandle> AppendTableRow(ITableHandle tableHandle);
Either<RfcErrorInfo, IFunctionHandle> CreateFunction(IFunctionDescriptionHandle descriptionHandle);
Expand All @@ -16,8 +17,15 @@ public interface IRfcRuntime
Either<RfcErrorInfo, IFunctionDescriptionHandle> GetFunctionDescription(IFunctionHandle functionHandle);
Either<RfcErrorInfo, string> GetFunctionName(IFunctionDescriptionHandle descriptionHandle);
Either<RfcErrorInfo, int> GetFunctionParameterCount(IFunctionDescriptionHandle descriptionHandle);
Either<RfcErrorInfo, IFunctionDescriptionHandle> CreateFunctionDescription(string functionName);
Either<RfcErrorInfo, IFunctionDescriptionHandle> AddFunctionParameter(IFunctionDescriptionHandle descriptionHandle, RfcParameterDescription parameterDescription);
Either<RfcErrorInfo, RfcParameterInfo> GetFunctionParameterDescription(IFunctionDescriptionHandle descriptionHandle, int index);
Either<RfcErrorInfo, RfcParameterInfo> GetFunctionParameterDescription(IFunctionDescriptionHandle descriptionHandle, string name);
Either<RfcErrorInfo, Unit> AddFunctionHandler(string sysid,
string functionName, IFunction function, Func<IFunction, Either<RfcErrorInfo, Unit>> handler);
Either<RfcErrorInfo, Unit> AddFunctionHandler(string sysid, string functionName,
IFunctionDescriptionHandle descriptionHandle,
Func<IFunction, Either<RfcErrorInfo, Unit>> handler);
Either<RfcErrorInfo, IStructureHandle> GetStructure(IDataContainerHandle dataContainer, string name);
Either<RfcErrorInfo, ITableHandle> GetTable(IDataContainerHandle dataContainer, string name);
Either<RfcErrorInfo, ITableHandle> CloneTable(ITableHandle tableHandle);
Expand Down Expand Up @@ -58,6 +66,7 @@ public interface IRfcRuntime
Either<RfcErrorInfo, T> GetFieldValue<T>(IDataContainerHandle handle, Func<Either<RfcErrorInfo, RfcFieldInfo>> func);

RfcRuntimeOptions Options { get; }
bool IsFunctionHandlerRegistered(string sysId, string functionName);
}


Expand Down
63 changes: 63 additions & 0 deletions src/YaNco.Core/CalledFunction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;
using LanguageExt;

namespace Dbosoft.YaNco
{
public readonly struct CalledFunction
{
public readonly IFunction Function;

internal CalledFunction(IFunction function)
{
Function = function;
}

public Either<RfcErrorInfo, FunctionInput<TInput>> Input<TInput>(Func<Either<RfcErrorInfo, IFunction>, Either<RfcErrorInfo, TInput>> inputFunc)
{
var function = Function;
return inputFunc(Prelude.Right(function)).Map(input => new FunctionInput<TInput>(input, function));
}


}

public readonly struct FunctionInput<TInput>
{
public readonly TInput Input;
public readonly IFunction Function;

internal FunctionInput(TInput input, IFunction function)
{
Input = input;
Function = function;
}

public FunctionProcessed<TOutput> Process<TOutput>(Func<TInput, TOutput> processFunc)
{
return new FunctionProcessed<TOutput>(processFunc(Input), Function);
}

public void Deconstruct(out IFunction function, out TInput input)
{
function = Function;
input = Input;
}
}

public readonly struct FunctionProcessed<TOutput>
{
private readonly TOutput _output;
private readonly IFunction _function;

internal FunctionProcessed(TOutput output, IFunction function)
{
_output = output;
_function = function;
}

public Either<RfcErrorInfo, Unit> Reply(Func<TOutput, Either<RfcErrorInfo, IFunction>, Either<RfcErrorInfo, IFunction>> replyFunc)
{
return replyFunc(_output, Prelude.Right(_function)).Map(_ => Unit.Default);
}
}
}
26 changes: 7 additions & 19 deletions src/YaNco.Core/Connection.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Dbosoft.Functional;
Expand Down Expand Up @@ -59,14 +60,6 @@ public Connection(
}
}

case AllowStartOfProgramsMessage allowStartOfProgramsMessage:
{
var result = rfcRuntime.AllowStartOfPrograms(handle,
allowStartOfProgramsMessage.Callback).Map(u => (object)u);
return (handle, result) ;

}

case DisposeMessage disposeMessage:
{
handle.Dispose();
Expand Down Expand Up @@ -165,8 +158,12 @@ public EitherAsync<RfcErrorInfo, Unit> InvokeFunction(IFunction function)
public EitherAsync<RfcErrorInfo, Unit> InvokeFunction(IFunction function, CancellationToken cancellationToken)
=> _stateAgent.Tell(new InvokeFunctionMessage(function, cancellationToken)).ToAsync().Map(_ => Unit.Default);

public EitherAsync<RfcErrorInfo, Unit> AllowStartOfPrograms(StartProgramDelegate callback) =>
_stateAgent.Tell(new AllowStartOfProgramsMessage(callback)).ToAsync().Map(r => Unit.Default);
[Obsolete("Use method WithStartProgramCallback of ConnectionBuilder instead. This method will be removed in next major release.")]
[ExcludeFromCodeCoverage]
public EitherAsync<RfcErrorInfo, Unit> AllowStartOfPrograms(StartProgramDelegate callback)
{
return RfcRuntime.AllowStartOfPrograms(_connectionHandle, callback).ToAsync();
}

public EitherAsync<RfcErrorInfo, ConnectionAttributes> GetAttributes()
{
Expand Down Expand Up @@ -200,15 +197,6 @@ public InvokeFunctionMessage(IFunction function, CancellationToken cancellationT
}
}

private class AllowStartOfProgramsMessage : AgentMessage
{
public readonly StartProgramDelegate Callback;

public AllowStartOfProgramsMessage(StartProgramDelegate callback)
{
Callback = callback;
}
}

private class DisposeMessage : AgentMessage
{
Expand Down
144 changes: 134 additions & 10 deletions src/YaNco.Core/ConnectionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,51 +4,175 @@

namespace Dbosoft.YaNco
{
/// <summary>
/// This class is used to build connections to a SAP ABAP backend.
/// </summary>
public class ConnectionBuilder
{
private readonly IDictionary<string, string> _connectionParam;
private StartProgramDelegate _startProgramDelegate;
private Action<RfcRuntimeConfigurer> _configureRuntime = (c) => {};
private Func<IDictionary<string, string>, IRfcRuntime, EitherAsync<RfcErrorInfo,IConnection>>
private Action<RfcRuntimeConfigurer> _configureRuntime = (c) => { };

private Func<IDictionary<string, string>, IRfcRuntime, EitherAsync<RfcErrorInfo, IConnection>>
_connectionFactory = Connection.Create;

readonly List<(string, Action<IFunctionBuilder>,
Func<CalledFunction, Either<RfcErrorInfo, Unit>>)> _functionHandlers
= new List<(string, Action<IFunctionBuilder>, Func<CalledFunction, Either<RfcErrorInfo, Unit>>)>();

/// <summary>
/// Creates a new connection builder.
/// </summary>
/// <param name="connectionParam">Dictionary of connection parameters</param>
public ConnectionBuilder(IDictionary<string, string> connectionParam)
{
_connectionParam = connectionParam;
}

/// <summary>
/// Registers a action to configure the <see cref="IRfcRuntime"/>
/// </summary>
/// <param name="configure">action with <see cref="RfcRuntimeConfigurer"/></param>
/// <returns>current instance for chaining.</returns>
/// <remarks>
/// Multiple calls of this method will override the previous configuration action.
/// </remarks>
public ConnectionBuilder ConfigureRuntime(Action<RfcRuntimeConfigurer> configure)
{
_configureRuntime = configure;
return this;
}

/// <summary>
/// This method registers a callback of type <see cref="StartProgramDelegate"/>
/// to handle backend requests to start local programs.
/// </summary>
/// <remarks>
/// The SAP backend can call function RFC_START_PROGRAM on back destination to request
/// clients to start local programs. This is used a lot in KPRO applications to start saphttp and sapftp.
/// </remarks>
/// <param name="startProgramDelegate">Delegate to callback function implementation.</param>
/// <returns>current instance for chaining</returns>
public ConnectionBuilder WithStartProgramCallback(StartProgramDelegate startProgramDelegate)
{
_startProgramDelegate = startProgramDelegate;
return WithFunctionHandler("RFC_START_PROGRAM", builder => builder
.AddChar("COMMAND", RfcDirection.Import, 512),
cf => cf
.Input(f => f.GetField<string>("COMMAND"))
.Process(cmd => startProgramDelegate(cmd))
.NoReply()
);
}

/// <summary>
/// This method registers a function handler from a SAP function name.
/// </summary>
/// <param name="functionName">Name of function</param>
/// <param name="calledFunc">function handler</param>
/// <returns>current instance for chaining</returns>
/// <remarks>
/// The metadata of the function is retrieved from the backend. Therefore the function
/// must exists on the SAP backend system.
/// To register a generic function use the signature that builds from a <see cref="IFunctionBuilder"/>.
/// Function handlers are registered process wide (in the SAP NW RFC Library).and mapped to backend system id.
/// Multiple registrations of same function and same backend id will therefore have no effect.
/// </remarks>
public ConnectionBuilder WithFunctionHandler(string functionName,
Func<CalledFunction, Either<RfcErrorInfo, Unit>> calledFunc)
{
_functionHandlers.Add((functionName, null, calledFunc));
return this;
}

/// <summary>
/// This method registers a function handler from a <see cref="IFunctionBuilder"/>
/// </summary>
/// <param name="functionName">Name of function</param>
/// <param name="configureBuilder">action to configure function builder</param>
/// <param name="calledFunc">function handler</param>
/// <returns>current instance for chaining</returns>
/// <remarks>
/// The metadata of the function is build in the <see cref="IFunctionBuilder"/>. This allows to register
/// any kind of function.
/// To register a known function use the signature with function name <seealso cref="WithFunctionHandler(string,System.Func{Dbosoft.YaNco.CalledFunction,LanguageExt.Either{Dbosoft.YaNco.RfcErrorInfo,LanguageExt.Unit}})"/>
/// Function handlers are registered process wide (in the SAP NW RFC Library) and mapped to backend system id.
/// Multiple registrations of same function and same backend id will therefore have no effect.
/// </remarks>
public ConnectionBuilder WithFunctionHandler(string functionName,
Action<IFunctionBuilder> configureBuilder,
Func<CalledFunction, Either<RfcErrorInfo, Unit>> calledFunc)
{
_functionHandlers.Add((functionName, configureBuilder, calledFunc));
return this;
}

/// <summary>
/// Use a alternative factory method to create connection.
/// </summary>
/// <param name="factory">factory method</param>
/// <returns>current instance for chaining.</returns
/// <remarks>The default implementation call <see cref="Connection.Create"/>.
/// </remarks>
public ConnectionBuilder UseFactory(
Func<IDictionary<string, string>, IRfcRuntime, EitherAsync<RfcErrorInfo, IConnection>> factory)
{
_connectionFactory = factory;
return this;
}

/// <summary>
/// This method Builds the connection function from the <see cref="ConnectionBuilder"/> settings.
/// </summary>
/// <returns>current instance for chaining.</returns>
/// <remarks>
/// The connection builder first creates RfcRuntime and calls any registered runtime configure action.
/// The result is a function that first calls the connection factory (defaults to <seealso cref="Connection.Create"/>
/// and afterwards registers function handlers.
/// </remarks>
public Func<EitherAsync<RfcErrorInfo, IConnection>> Build()
{
var runtimeConfigurer = new RfcRuntimeConfigurer();
_configureRuntime(runtimeConfigurer);
var runtime = runtimeConfigurer.Create();

if(_startProgramDelegate == null)
return () => _connectionFactory(_connectionParam, runtime);

return () => _connectionFactory(_connectionParam, runtime)
.Bind(RegisterFunctionHandlers);

}

private EitherAsync<RfcErrorInfo, IConnection> RegisterFunctionHandlers(IConnection connection)
{
return connection.GetAttributes().Bind(attributes =>
{
return _functionHandlers.Map(reg =>
{
var (functionName, configureBuilder, callBackFunction) = reg;

if (connection.RfcRuntime.IsFunctionHandlerRegistered(attributes.SystemId, functionName))
return Unit.Default;

if (configureBuilder != null)
{
var builder = new FunctionBuilder(connection.RfcRuntime, functionName);
configureBuilder(builder);
return builder.Build().ToAsync().Bind(descr =>
{
return connection.RfcRuntime.AddFunctionHandler(attributes.SystemId,
functionName,
descr,
f => callBackFunction(new CalledFunction(f))).ToAsync();
});

}

return () => (from c in _connectionFactory(_connectionParam, runtime)
from _ in c.AllowStartOfPrograms(_startProgramDelegate)
select c);
return connection.CreateFunction(functionName).Bind(func =>
{
return connection.RfcRuntime.AddFunctionHandler(attributes.SystemId,
functionName,
func,
f => callBackFunction(new CalledFunction(f))).ToAsync();
});
}).Traverse(l => l).Map(eu => connection);
});
}
}
}
Loading