2019-10-27 14:38:37 +00:00
|
|
|
using System;
|
2019-11-17 19:43:16 +00:00
|
|
|
using System.Dynamic;
|
2019-10-27 14:38:37 +00:00
|
|
|
using System.Linq;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
2019-11-17 19:43:16 +00:00
|
|
|
using AutoMapper;
|
2019-10-27 14:38:37 +00:00
|
|
|
using Elsa.Expressions;
|
2019-11-17 19:43:16 +00:00
|
|
|
using Elsa.Scripting.JavaScript.Converters;
|
2019-11-19 07:42:28 +00:00
|
|
|
using Elsa.Scripting.JavaScript.Extensions;
|
2019-10-27 14:38:37 +00:00
|
|
|
using Elsa.Scripting.JavaScript.Messages;
|
|
|
|
|
using Elsa.Services;
|
|
|
|
|
using Elsa.Services.Models;
|
|
|
|
|
using Jint;
|
|
|
|
|
using Jint.Native;
|
|
|
|
|
using MediatR;
|
|
|
|
|
using Newtonsoft.Json;
|
2019-11-18 07:07:33 +00:00
|
|
|
using Newtonsoft.Json.Linq;
|
2019-10-27 14:38:37 +00:00
|
|
|
using NodaTime;
|
|
|
|
|
using NodaTime.Serialization.JsonNet;
|
|
|
|
|
|
|
|
|
|
namespace Elsa.Scripting.JavaScript.Services
|
|
|
|
|
{
|
|
|
|
|
public class JavaScriptExpressionEvaluator : IExpressionEvaluator
|
|
|
|
|
{
|
|
|
|
|
private readonly IMediator mediator;
|
2019-11-17 19:43:16 +00:00
|
|
|
private readonly IMapper mapper;
|
2019-10-27 14:38:37 +00:00
|
|
|
private readonly JsonSerializerSettings serializerSettings;
|
|
|
|
|
public const string SyntaxName = "JavaScript";
|
|
|
|
|
|
|
|
|
|
public static WorkflowExpression<T> CreateExpression<T>(string expression)
|
|
|
|
|
{
|
|
|
|
|
return new WorkflowExpression<T>(SyntaxName, expression);
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-17 19:43:16 +00:00
|
|
|
public JavaScriptExpressionEvaluator(IMediator mediator, IMapper mapper)
|
2019-10-27 14:38:37 +00:00
|
|
|
{
|
|
|
|
|
this.mediator = mediator;
|
2019-11-17 19:43:16 +00:00
|
|
|
this.mapper = mapper;
|
2019-11-18 07:07:33 +00:00
|
|
|
|
2019-10-27 14:38:37 +00:00
|
|
|
serializerSettings = new JsonSerializerSettings().ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
|
2019-11-17 19:43:16 +00:00
|
|
|
serializerSettings.Converters.Add(new TruncatingNumberJsonConverter());
|
2019-10-27 14:38:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string Syntax => SyntaxName;
|
|
|
|
|
|
|
|
|
|
public async Task<object> EvaluateAsync(string expression, Type type, WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
var engine = new Engine(options => { options.AllowClr(); });
|
|
|
|
|
|
|
|
|
|
await ConfigureEngineAsync(engine, workflowExecutionContext, cancellationToken);
|
|
|
|
|
engine.Execute(expression);
|
|
|
|
|
|
|
|
|
|
return ConvertValue(engine.GetCompletionValue(), type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task ConfigureEngineAsync(Engine engine, WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
await mediator.Publish(new EvaluatingJavaScriptExpression(engine, workflowExecutionContext), cancellationToken);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private object ConvertValue(JsValue value, Type targetType)
|
|
|
|
|
{
|
|
|
|
|
if (value.IsUndefined())
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
if (value.IsNull())
|
|
|
|
|
return default;
|
|
|
|
|
|
2019-11-19 07:42:28 +00:00
|
|
|
if (targetType == typeof(bool) && value.IsBoolean())
|
2019-10-27 14:38:37 +00:00
|
|
|
return value.AsBoolean();
|
|
|
|
|
|
2019-11-19 07:42:28 +00:00
|
|
|
if (targetType == typeof(DateTime) && value.IsDate())
|
2019-10-27 14:38:37 +00:00
|
|
|
return value.AsDate().ToDateTime();
|
|
|
|
|
|
2019-11-19 07:42:28 +00:00
|
|
|
if (targetType.IsNumeric() && value.IsNumber())
|
|
|
|
|
return Convert.ChangeType(value.AsNumber(), targetType);
|
|
|
|
|
|
|
|
|
|
if (targetType == typeof(string))
|
|
|
|
|
return value.ToString();
|
2019-10-27 14:38:37 +00:00
|
|
|
|
|
|
|
|
if (value.IsString())
|
|
|
|
|
{
|
|
|
|
|
var stringValue = value.AsString();
|
|
|
|
|
return targetType != null
|
|
|
|
|
? targetType == typeof(Uri)
|
|
|
|
|
? new Uri(stringValue, UriKind.RelativeOrAbsolute)
|
|
|
|
|
: Convert.ChangeType(stringValue, targetType)
|
|
|
|
|
: value.AsString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (value.IsArray())
|
|
|
|
|
{
|
|
|
|
|
var arrayInstance = value.AsArray();
|
|
|
|
|
var elementType = targetType.GetElementType() ?? targetType.GenericTypeArguments.First();
|
|
|
|
|
|
|
|
|
|
if (elementType == typeof(byte))
|
|
|
|
|
{
|
|
|
|
|
var bytes = new byte[arrayInstance.Length];
|
2019-11-17 19:43:16 +00:00
|
|
|
|
2019-10-27 14:38:37 +00:00
|
|
|
for (uint i = 0; i < arrayInstance.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
var jsValue = arrayInstance[i];
|
2019-11-17 19:43:16 +00:00
|
|
|
bytes[i] = (byte) jsValue.AsNumber();
|
2019-10-27 14:38:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return bytes;
|
|
|
|
|
}
|
2019-11-17 19:43:16 +00:00
|
|
|
|
2019-10-27 14:38:37 +00:00
|
|
|
var array = Array.CreateInstance(elementType, arrayInstance.Length);
|
|
|
|
|
|
|
|
|
|
for (uint i = 0; i < array.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
var jsValue = arrayInstance[i];
|
|
|
|
|
var convertedValue = ConvertValue(jsValue, elementType);
|
|
|
|
|
array.SetValue(convertedValue, i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return array;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (value.IsObject())
|
|
|
|
|
{
|
|
|
|
|
var obj = value.AsObject().ToObject();
|
2019-11-06 20:11:16 +00:00
|
|
|
|
2019-11-18 07:07:33 +00:00
|
|
|
switch (obj)
|
2019-11-17 19:43:16 +00:00
|
|
|
{
|
2019-11-18 07:07:33 +00:00
|
|
|
case ExpandoObject _:
|
|
|
|
|
{
|
|
|
|
|
var json = JsonConvert.SerializeObject(obj, serializerSettings);
|
|
|
|
|
return JsonConvert.DeserializeObject(json, targetType, serializerSettings);
|
|
|
|
|
}
|
|
|
|
|
case JValue jValue:
|
|
|
|
|
return jValue.Value;
|
|
|
|
|
default:
|
|
|
|
|
return obj;
|
2019-11-17 19:43:16 +00:00
|
|
|
}
|
2019-10-27 14:38:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw new ArgumentException($"Value type {value.Type} is not supported.", nameof(value));
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-10-15 12:15:36 +00:00
|
|
|
}
|