diff --git a/Samples.sln b/Samples.sln index e663b041a..6c1abb33c 100644 --- a/Samples.sln +++ b/Samples.sln @@ -110,6 +110,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.CorrelationHtt EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.ContextualWorkflowHttp", "src\samples\Elsa.Samples.ContextualWorkflowHttp\Elsa.Samples.ContextualWorkflowHttp.csproj", "{37753BF9-9D9B-4264-AB5B-D057E467D3C5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.RunChildWorkflowWorker", "src\samples\Elsa.Samples.RunChildWorkflowWorker\Elsa.Samples.RunChildWorkflowWorker.csproj", "{1E5E1669-AEC9-4DA9-9A3D-3D405101A246}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -261,6 +263,10 @@ Global {37753BF9-9D9B-4264-AB5B-D057E467D3C5}.Debug|Any CPU.Build.0 = Debug|Any CPU {37753BF9-9D9B-4264-AB5B-D057E467D3C5}.Release|Any CPU.ActiveCfg = Release|Any CPU {37753BF9-9D9B-4264-AB5B-D057E467D3C5}.Release|Any CPU.Build.0 = Release|Any CPU + {1E5E1669-AEC9-4DA9-9A3D-3D405101A246}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E5E1669-AEC9-4DA9-9A3D-3D405101A246}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E5E1669-AEC9-4DA9-9A3D-3D405101A246}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E5E1669-AEC9-4DA9-9A3D-3D405101A246}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -312,6 +318,7 @@ Global {E508C1B6-916F-40F4-8731-58279B8F45B2} = {5E5E1E84-DDBC-40D6-B891-0D563A15A44A} {565E2F7D-1F5F-45EF-B130-BC09CB3465B1} = {5E5E1E84-DDBC-40D6-B891-0D563A15A44A} {37753BF9-9D9B-4264-AB5B-D057E467D3C5} = {5E5E1E84-DDBC-40D6-B891-0D563A15A44A} + {1E5E1669-AEC9-4DA9-9A3D-3D405101A246} = {5E5E1E84-DDBC-40D6-B891-0D563A15A44A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8B0975FD-7050-48B0-88C5-48C33378E158} diff --git a/src/core/Elsa.Abstractions/Services/IWorkflowScheduler.cs b/src/core/Elsa.Abstractions/Services/IWorkflowScheduler.cs index f9ad0fc19..532d98932 100644 --- a/src/core/Elsa.Abstractions/Services/IWorkflowScheduler.cs +++ b/src/core/Elsa.Abstractions/Services/IWorkflowScheduler.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Elsa.Models; using Elsa.Triggers; namespace Elsa.Services @@ -11,17 +13,17 @@ namespace Elsa.Services /// Schedules new and existing instances matching the specified trigger. /// Task TriggerWorkflowsAsync(Func predicate, object? input = default, string? correlationId = default, string? contextId = default, CancellationToken cancellationToken = default) where TTrigger : ITrigger; - + /// /// Schedules the specified workflow instance for execution. /// Task ScheduleWorkflowInstanceAsync(string instanceId, string? activityId = default, object? input = default, CancellationToken cancellationToken = default); - + /// /// Creates a new workflow instance of the specified workflow definition and schedules it for execution. /// - Task ScheduleWorkflowDefinitionAsync(string definitionId, object? input = default, string? correlationId = default, string? contextId = default, CancellationToken cancellationToken = default); - + Task> ScheduleWorkflowDefinitionAsync(string definitionId, object? input = default, string? correlationId = default, string? contextId = default, CancellationToken cancellationToken = default); + /// /// Schedules new workflows that start with the specified activity type or are blocked on the specified activity type. /// diff --git a/src/core/Elsa.Core/Activities/Workflows/RunWorkflow/RunWorkflow.cs b/src/core/Elsa.Core/Activities/Workflows/RunWorkflow/RunWorkflow.cs new file mode 100644 index 000000000..64ffe5f1a --- /dev/null +++ b/src/core/Elsa.Core/Activities/Workflows/RunWorkflow/RunWorkflow.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Elsa.ActivityResults; +using Elsa.Attributes; +using Elsa.Services; +using Elsa.Services.Models; +using Open.Linq.AsyncExtensions; + +// ReSharper disable once CheckNamespace +namespace Elsa.Activities.Workflows +{ + [ActivityDefinition( + Category = "Workflows", + Description = "Runs a child workflow." + )] + public class RunWorkflow : Activity + { + private readonly IWorkflowScheduler _workflowScheduler; + + public RunWorkflow(IWorkflowScheduler workflowScheduler) + { + _workflowScheduler = workflowScheduler; + } + + [ActivityProperty] public string WorkflowDefinitionId { get; set; } = default!; + [ActivityProperty] public object? Input { get; set; } + [ActivityProperty] public string? CorrelationId { get; set; } + [ActivityProperty] public string? ContextId { get; set; } + [ActivityProperty] public RunWorkflowMode Mode { get; set; } + + public ICollection WorkflowInstanceIds + { + get => GetState>(); + set => SetState(value); + } + + protected override async ValueTask OnExecuteAsync(ActivityExecutionContext context, CancellationToken cancellationToken) + { + var workflowInstances = await _workflowScheduler.ScheduleWorkflowDefinitionAsync(WorkflowDefinitionId, Input, CorrelationId, ContextId, cancellationToken).ToList(); + WorkflowInstanceIds = workflowInstances.Select(x => x.WorkflowInstanceId).ToList(); + + return Mode switch + { + RunWorkflowMode.FireAndForget => Done(), + RunWorkflowMode.Blocking => Suspend(), + _ => Suspend() + }; + } + + protected override ValueTask OnResumeAsync(ActivityExecutionContext context, CancellationToken cancellationToken) + { + return base.OnResumeAsync(context, cancellationToken); + } + + public enum RunWorkflowMode + { + /// + /// Run the specified workflow and continue with the current one. + /// + FireAndForget, + + /// + /// Run the specified workflow and continue once the child workflow finishes. + /// + Blocking + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/Workflows/RunWorkflow/RunWorkflowBuilderExtensions.cs b/src/core/Elsa.Core/Activities/Workflows/RunWorkflow/RunWorkflowBuilderExtensions.cs new file mode 100644 index 000000000..7530fbf9d --- /dev/null +++ b/src/core/Elsa.Core/Activities/Workflows/RunWorkflow/RunWorkflowBuilderExtensions.cs @@ -0,0 +1,24 @@ +using System; +using Elsa.Activities.ControlFlow; +using Elsa.Builders; +using Elsa.Services.Models; + +// ReSharper disable once CheckNamespace +namespace Elsa.Activities.Workflows +{ + public static class RunWorkflowBuilderExtensions + { + public static IActivityBuilder RunWorkflow(this IBuilder builder, Action>? setup = default) => builder.Then(setup); + public static IActivityBuilder RunWorkflow(this IBuilder builder, RunWorkflow.RunWorkflowMode mode) => builder.RunWorkflow(activity => activity.WithWorkflow().WithMode(mode)); + public static IActivityBuilder RunWorkflow(this IBuilder builder, RunWorkflow.RunWorkflowMode mode, object input) => builder.RunWorkflow(activity => activity.WithWorkflow().WithMode(mode).WithInput(input)); + + public static IActivityBuilder RunWorkflow(this IBuilder builder, RunWorkflow.RunWorkflowMode mode, object input, string correlationId) => + builder.RunWorkflow(activity => activity.WithWorkflow().WithMode(mode).WithInput(input).WithCorrelationId(correlationId)); + + public static IActivityBuilder RunWorkflow(this IBuilder builder, RunWorkflow.RunWorkflowMode mode, object input, string correlationId, string contextId) => + builder.RunWorkflow(activity => activity.WithWorkflow().WithMode(mode).WithInput(input).WithCorrelationId(correlationId).WithContextId(contextId)); + + public static IActivityBuilder RunWorkflow(this IBuilder builder, RunWorkflow.RunWorkflowMode mode, string correlationId) => + builder.RunWorkflow(activity => activity.WithWorkflow().WithMode(mode).WithCorrelationId(correlationId)); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/Workflows/RunWorkflow/RunWorkflowExtensions.cs b/src/core/Elsa.Core/Activities/Workflows/RunWorkflow/RunWorkflowExtensions.cs new file mode 100644 index 000000000..a7bf3693e --- /dev/null +++ b/src/core/Elsa.Core/Activities/Workflows/RunWorkflow/RunWorkflowExtensions.cs @@ -0,0 +1,53 @@ +using System; +using System.Threading.Tasks; +using Elsa.Activities.Workflows; +using Elsa.Builders; +using Elsa.Extensions; +using Elsa.Services; +using Elsa.Services.Models; + +// ReSharper disable once CheckNamespace +namespace Elsa.Activities.ControlFlow +{ + public static class RunWorkflowExtensions + { + public static ISetupActivity WithWorkflow(this ISetupActivity activity, Func> value) => activity.Set(x => x.WorkflowDefinitionId, value); + public static ISetupActivity WithWorkflow(this ISetupActivity activity, Func value) => activity.Set(x => x.WorkflowDefinitionId, value); + public static ISetupActivity WithWorkflow(this ISetupActivity activity, Func> value) => activity.Set(x => x.WorkflowDefinitionId, value); + public static ISetupActivity WithWorkflow(this ISetupActivity activity, Func value) => activity.Set(x => x.WorkflowDefinitionId, value); + public static ISetupActivity WithWorkflow(this ISetupActivity activity, string value) => activity.Set(x => x.WorkflowDefinitionId, value); + + public static ISetupActivity WithWorkflow(this ISetupActivity activity) => + activity.WithWorkflow( + async context => + { + var workflowRegistry = context.GetService(); + var workflow = (await workflowRegistry.GetWorkflowAsync())!; + return workflow.Id; + }); + + public static ISetupActivity WithInput(this ISetupActivity activity, Func> value) => activity.Set(x => x.Input, value); + public static ISetupActivity WithInput(this ISetupActivity activity, Func value) => activity.Set(x => x.Input, value); + public static ISetupActivity WithInput(this ISetupActivity activity, Func> value) => activity.Set(x => x.Input, value); + public static ISetupActivity WithInput(this ISetupActivity activity, Func value) => activity.Set(x => x.Input, value); + public static ISetupActivity WithInput(this ISetupActivity activity, object? value) => activity.Set(x => x.Input, value); + + public static ISetupActivity WithCorrelationId(this ISetupActivity activity, Func> value) => activity.Set(x => x.CorrelationId, value); + public static ISetupActivity WithCorrelationId(this ISetupActivity activity, Func value) => activity.Set(x => x.CorrelationId, value); + public static ISetupActivity WithCorrelationId(this ISetupActivity activity, Func> value) => activity.Set(x => x.CorrelationId, value); + public static ISetupActivity WithCorrelationId(this ISetupActivity activity, Func value) => activity.Set(x => x.CorrelationId, value); + public static ISetupActivity WithCorrelationId(this ISetupActivity activity, string? value) => activity.Set(x => x.CorrelationId, value); + + public static ISetupActivity WithContextId(this ISetupActivity activity, Func> value) => activity.Set(x => x.ContextId, value); + public static ISetupActivity WithContextId(this ISetupActivity activity, Func value) => activity.Set(x => x.ContextId, value); + public static ISetupActivity WithContextId(this ISetupActivity activity, Func> value) => activity.Set(x => x.ContextId, value); + public static ISetupActivity WithContextId(this ISetupActivity activity, Func value) => activity.Set(x => x.ContextId, value); + public static ISetupActivity WithContextId(this ISetupActivity activity, string? value) => activity.Set(x => x.ContextId, value); + + public static ISetupActivity WithMode(this ISetupActivity activity, Func> value) => activity.Set(x => x.Mode, value); + public static ISetupActivity WithMode(this ISetupActivity activity, Func value) => activity.Set(x => x.Mode, value); + public static ISetupActivity WithMode(this ISetupActivity activity, Func> value) => activity.Set(x => x.Mode, value); + public static ISetupActivity WithMode(this ISetupActivity activity, Func value) => activity.Set(x => x.Mode, value); + public static ISetupActivity WithMode(this ISetupActivity activity, RunWorkflow.RunWorkflowMode value) => activity.Set(x => x.Mode, value); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs b/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs index 138179442..f9a2e41a7 100644 --- a/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs +++ b/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs @@ -122,7 +122,7 @@ namespace Microsoft.Extensions.DependencyInjection .AddStartupTask() .AddConsumer() .AddMetadataHandlers() - .AddPrimitiveActivities(); + .AddCoreActivities(); return configuration; } @@ -131,7 +131,7 @@ namespace Microsoft.Extensions.DependencyInjection services .AddSingleton(); - private static IServiceCollection AddPrimitiveActivities(this IServiceCollection services) => + private static IServiceCollection AddCoreActivities(this IServiceCollection services) => services .AddActivity() .AddActivity() @@ -148,7 +148,8 @@ namespace Microsoft.Extensions.DependencyInjection .AddActivity() .AddTriggerProvider() .AddActivity() - .AddActivity(); + .AddActivity() + .AddActivity(); private static ElsaOptions AddServiceBus(this ElsaOptions options) { diff --git a/src/core/Elsa.Core/Extensions/WorkflowRegistryExtensions.cs b/src/core/Elsa.Core/Extensions/WorkflowRegistryExtensions.cs index 258861fae..930447b47 100644 --- a/src/core/Elsa.Core/Extensions/WorkflowRegistryExtensions.cs +++ b/src/core/Elsa.Core/Extensions/WorkflowRegistryExtensions.cs @@ -12,7 +12,7 @@ namespace Elsa.Extensions { public static Task GetWorkflowAsync( this IWorkflowRegistry workflowRegistry, - CancellationToken cancellationToken) => + CancellationToken cancellationToken = default) => workflowRegistry.GetWorkflowAsync(typeof(T).Name, VersionOptions.Latest, cancellationToken); public static async Task> @@ -25,11 +25,10 @@ namespace Elsa.Extensions return results.Select(x => (x.Workflow, x.Activity)); } - public static async Task> - GetWorkflowsByStartActivityAsync( - this IWorkflowRegistry workflowRegistry, - string activityType, - CancellationToken cancellationToken = default) + public static async Task> GetWorkflowsByStartActivityAsync( + this IWorkflowRegistry workflowRegistry, + string activityType, + CancellationToken cancellationToken = default) { var workflows = await workflowRegistry.GetWorkflowsAsync(cancellationToken).ToListAsync(cancellationToken); diff --git a/src/core/Elsa.Core/Services/WorkflowScheduler.cs b/src/core/Elsa.Core/Services/WorkflowScheduler.cs index cdc574c43..b489d7a89 100644 --- a/src/core/Elsa.Core/Services/WorkflowScheduler.cs +++ b/src/core/Elsa.Core/Services/WorkflowScheduler.cs @@ -50,7 +50,7 @@ namespace Elsa.Services CancellationToken cancellationToken = default) => await _serviceBus.Publish(new RunWorkflow(instanceId, activityId, input)); - public async Task ScheduleWorkflowDefinitionAsync( + public async Task> ScheduleWorkflowDefinitionAsync( string definitionId, object? input = default, string? correlationId = default, @@ -66,9 +66,15 @@ namespace Elsa.Services throw new WorkflowException($"No workflow definition found by ID {definitionId}"); var startActivities = workflow.GetStartActivities(); + var workflowInstances = new List(); foreach (var activity in startActivities) - await ScheduleWorkflowAsync(workflow, activity, input, correlationId, contextId, cancellationToken); + { + var workflowInstance = await ScheduleWorkflowAsync(workflow, activity, input, correlationId, contextId, cancellationToken); + workflowInstances.Add(workflowInstance); + } + + return workflowInstances; } public async Task TriggerWorkflowsAsync( @@ -188,7 +194,7 @@ namespace Elsa.Services cancellationToken); } - private async Task ScheduleWorkflowAsync( + private async Task ScheduleWorkflowAsync( IWorkflowBlueprint workflowBlueprint, IActivityBlueprint activity, object? input, @@ -199,6 +205,7 @@ namespace Elsa.Services var workflowInstance = await _workflowFactory.InstantiateAsync(workflowBlueprint, correlationId, contextId, cancellationToken); await _workflowInstanceManager.SaveAsync(workflowInstance, cancellationToken); await ScheduleWorkflowInstanceAsync(workflowInstance.WorkflowInstanceId, activity.Id, input, cancellationToken); + return workflowInstance; } private async Task> FilterRunningSingletonsAsync(IEnumerable workflows) diff --git a/src/samples/Elsa.Samples.RunChildWorkflowWorker/Elsa.Samples.RunChildWorkflowWorker.csproj b/src/samples/Elsa.Samples.RunChildWorkflowWorker/Elsa.Samples.RunChildWorkflowWorker.csproj new file mode 100644 index 000000000..56d0b862e --- /dev/null +++ b/src/samples/Elsa.Samples.RunChildWorkflowWorker/Elsa.Samples.RunChildWorkflowWorker.csproj @@ -0,0 +1,14 @@ + + + + net5.0 + + + + + + + + + + diff --git a/src/samples/Elsa.Samples.RunChildWorkflowWorker/HostedServices/RunParentWorkflow.cs b/src/samples/Elsa.Samples.RunChildWorkflowWorker/HostedServices/RunParentWorkflow.cs new file mode 100644 index 000000000..66064f424 --- /dev/null +++ b/src/samples/Elsa.Samples.RunChildWorkflowWorker/HostedServices/RunParentWorkflow.cs @@ -0,0 +1,32 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Samples.RunChildWorkflowWorker.Workflows; +using Elsa.Services; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Elsa.Samples.RunChildWorkflowWorker.HostedServices +{ + /// + /// Runs the parent workflow. + /// + public class RunParentWorkflow : IHostedService + { + private readonly IServiceProvider _serviceProvider; + + public RunParentWorkflow(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + using var scope = _serviceProvider.CreateScope(); + var workflowRunner = scope.ServiceProvider.GetRequiredService(); + await workflowRunner.RunWorkflowAsync(cancellationToken: cancellationToken); + } + + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/samples/Elsa.Samples.RunChildWorkflowWorker/Program.cs b/src/samples/Elsa.Samples.RunChildWorkflowWorker/Program.cs new file mode 100644 index 000000000..fe9516fb5 --- /dev/null +++ b/src/samples/Elsa.Samples.RunChildWorkflowWorker/Program.cs @@ -0,0 +1,31 @@ +using System.Data; +using Elsa.Samples.RunChildWorkflowWorker.HostedServices; +using Elsa.Samples.RunChildWorkflowWorker.Workflows; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using NodaTime; +using YesSql.Provider.Sqlite; + +namespace Elsa.Samples.RunChildWorkflowWorker +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureServices( + (hostContext, services) => + { + services + .AddElsa(options => options.UsePersistence(db => db.UseSqLite("Data Source=elsa.db;Cache=Shared", IsolationLevel.ReadUncommitted))) + .AddConsoleActivities() + .AddHostedService() + .AddWorkflow() + .AddWorkflow(); + }); + } +} \ No newline at end of file diff --git a/src/samples/Elsa.Samples.RunChildWorkflowWorker/Properties/launchSettings.json b/src/samples/Elsa.Samples.RunChildWorkflowWorker/Properties/launchSettings.json new file mode 100644 index 000000000..505149918 --- /dev/null +++ b/src/samples/Elsa.Samples.RunChildWorkflowWorker/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "Elsa.Samples.WhileLoopWorker": { + "commandName": "Project", + "dotnetRunMessages": "true", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/samples/Elsa.Samples.RunChildWorkflowWorker/Workflows/ParentWorkflow.cs b/src/samples/Elsa.Samples.RunChildWorkflowWorker/Workflows/ParentWorkflow.cs new file mode 100644 index 000000000..1a09a0a64 --- /dev/null +++ b/src/samples/Elsa.Samples.RunChildWorkflowWorker/Workflows/ParentWorkflow.cs @@ -0,0 +1,42 @@ +using Elsa.Activities.Console; +using Elsa.Activities.ControlFlow; +using Elsa.Activities.Timers; +using Elsa.Activities.Workflows; +using Elsa.Builders; +using NodaTime; + +namespace Elsa.Samples.RunChildWorkflowWorker.Workflows +{ + /// + /// Delegate the hard work of counting numbers to a child workflow. + /// + public class ParentWorkflow : IWorkflow + { + private const int Count = 3; + + public void Build(IWorkflowBuilder workflow) + { + workflow + .WriteLine("This is the parent workflow.") + .WriteLine("Let's kick off the child workflow.") + .RunWorkflow(RunWorkflow.RunWorkflowMode.Blocking, Count) + .WriteLine("Parent finished."); + } + } + + public class ChildWorkflow : IWorkflowV + { + public void Build(IWorkflowBuilder workflow) + { + workflow + .SetVariable("Count", context => (int)context.Input!) + .WriteLine(context => $"Child workflow counting down from {context.GetVariable("Count")} to 0") + .For(context => context.GetVariable("Count"), _ => 0, + iterate => + { + iterate.WriteLine(context => $"{context.Input}"); + }) + .WriteLine("Done. Back to you, parent workflow!"); + } + } +} \ No newline at end of file diff --git a/src/samples/Elsa.Samples.RunChildWorkflowWorker/appsettings.Development.json b/src/samples/Elsa.Samples.RunChildWorkflowWorker/appsettings.Development.json new file mode 100644 index 000000000..8983e0fc1 --- /dev/null +++ b/src/samples/Elsa.Samples.RunChildWorkflowWorker/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/src/samples/Elsa.Samples.RunChildWorkflowWorker/appsettings.json b/src/samples/Elsa.Samples.RunChildWorkflowWorker/appsettings.json new file mode 100644 index 000000000..8983e0fc1 --- /dev/null +++ b/src/samples/Elsa.Samples.RunChildWorkflowWorker/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +}