15
15
using Microsoft . Extensions . Hosting ;
16
16
using Microsoft . Extensions . Logging ;
17
17
using Microsoft . Extensions . Options ;
18
+ using Neuroglia . Serialization ;
18
19
using ServerlessWorkflow . Sdk . IO ;
19
20
using ServerlessWorkflow . Sdk . Models ;
20
21
using Synapse . Api . Application . Configuration ;
@@ -28,9 +29,11 @@ namespace Synapse.Api.Application.Services;
28
29
/// </summary>
29
30
/// <param name="serviceProvider">The current <see cref="IServiceProvider"/></param>
30
31
/// <param name="logger">The service used to perform logging</param>
32
+ /// <param name="jsonSerializer">The service used to serialize/deserialize data to/from JSON</param>
33
+ /// <param name="yamlSerializer">The service used to serialize/deserialize data to/from YAML</param>
31
34
/// <param name="workflowDefinitionReader">The service used to read <see cref="WorkflowDefinition"/>s</param>
32
35
/// <param name="options">The service used to access the current <see cref="ApiServerOptions"/></param>
33
- public class WorkflowDatabaseInitializer ( IServiceProvider serviceProvider , ILogger < WorkflowDatabaseInitializer > logger , IWorkflowDefinitionReader workflowDefinitionReader , IOptions < ApiServerOptions > options )
36
+ public class WorkflowDatabaseInitializer ( IServiceProvider serviceProvider , ILogger < WorkflowDatabaseInitializer > logger , IJsonSerializer jsonSerializer , IYamlSerializer yamlSerializer , IWorkflowDefinitionReader workflowDefinitionReader , IOptions < ApiServerOptions > options )
34
37
: IHostedService
35
38
{
36
39
@@ -44,6 +47,16 @@ public class WorkflowDatabaseInitializer(IServiceProvider serviceProvider, ILogg
44
47
/// </summary>
45
48
protected ILogger Logger { get ; } = logger ;
46
49
50
+ /// <summary>
51
+ /// Gets the service used to serialize/deserialize data to/from JSON
52
+ /// </summary>
53
+ protected IJsonSerializer JsonSerializer { get ; } = jsonSerializer ;
54
+
55
+ /// <summary>
56
+ /// Gets the service used to serialize/deserialize data to/from YAML
57
+ /// </summary>
58
+ protected IYamlSerializer YamlSerializer { get ; } = yamlSerializer ;
59
+
47
60
/// <summary>
48
61
/// Gets the service used to read <see cref="WorkflowDefinition"/>s
49
62
/// </summary>
@@ -80,15 +93,80 @@ public virtual async Task StartAsync(CancellationToken cancellationToken)
80
93
this . Logger . LogWarning ( "The directory '{directory}' does not exist or cannot be found. Skipping static resource import" , directory . FullName ) ;
81
94
return ;
82
95
}
83
- this . Logger . LogInformation ( "Starting importing static resources from directory '{directory}'..." , directory . FullName ) ;
96
+ await this . ProvisionNamespacesAsync ( resources , cancellationToken ) . ConfigureAwait ( false ) ;
97
+ await this . ProvisionWorkflowsAsync ( resources , cancellationToken ) . ConfigureAwait ( false ) ;
98
+ await this . ProvisionFunctionsAsync ( resources , cancellationToken ) . ConfigureAwait ( false ) ;
99
+ }
100
+
101
+ /// <summary>
102
+ /// Provisions namespaces from statis resource files
103
+ /// </summary>
104
+ /// <param name="resources">The <see cref="IResourceRepository"/> used to manage <see cref="IResource"/>s</param>
105
+ /// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
106
+ /// <returns>A new awaitable <see cref="Task"/></returns>
107
+ protected virtual async Task ProvisionNamespacesAsync ( IResourceRepository resources , CancellationToken cancellationToken )
108
+ {
109
+ var stopwatch = new Stopwatch ( ) ;
110
+ var directory = new DirectoryInfo ( Path . Combine ( this . Options . Seeding . Directory , "namespaces" ) ) ;
111
+ if ( ! directory . Exists ) return ;
112
+ this . Logger . LogInformation ( "Starting importing namespaces from directory '{directory}'..." , directory . FullName ) ;
84
113
var files = directory . GetFiles ( this . Options . Seeding . FilePattern , SearchOption . AllDirectories ) . Where ( f => f . FullName . EndsWith ( ".json" , StringComparison . OrdinalIgnoreCase ) || f . FullName . EndsWith ( ".yml" , StringComparison . OrdinalIgnoreCase ) || f . FullName . EndsWith ( ".yaml" , StringComparison . OrdinalIgnoreCase ) ) ;
85
114
if ( ! files . Any ( ) )
86
115
{
87
- this . Logger . LogWarning ( "No static resource files matching search pattern '{pattern}' found in directory '{directory}'. Skipping import." , this . Options . Seeding . FilePattern , directory . FullName ) ;
116
+ this . Logger . LogWarning ( "No namespace static resource files matching search pattern '{pattern}' found in directory '{directory}'. Skipping import." , this . Options . Seeding . FilePattern , directory . FullName ) ;
88
117
return ;
89
118
}
119
+ stopwatch . Restart ( ) ;
90
120
var count = 0 ;
121
+ foreach ( var file in files )
122
+ {
123
+ try
124
+ {
125
+ var extension = file . FullName . Split ( '.' , StringSplitOptions . RemoveEmptyEntries ) . LastOrDefault ( ) ;
126
+ var serializer = extension ? . ToLowerInvariant ( ) switch
127
+ {
128
+ "json" => ( ITextSerializer ) this . JsonSerializer ,
129
+ "yml" or "yaml" => this . YamlSerializer ,
130
+ _ => throw new NotSupportedException ( $ "The specified extension '{ extension } ' is not supported for static resource files")
131
+ } ;
132
+ using var stream = file . OpenRead ( ) ;
133
+ using var streamReader = new StreamReader ( stream ) ;
134
+ var text = await streamReader . ReadToEndAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
135
+ var ns = serializer . Deserialize < NamespaceDefinition > ( text ) ! ;
136
+ await resources . AddAsync ( ns , false , cancellationToken ) . ConfigureAwait ( false ) ;
137
+ this . Logger . LogInformation ( "Successfully imported namespace '{namespace}' from file '{file}'" , $ "{ ns . Metadata . Name } ", file . FullName ) ;
138
+ count ++ ;
139
+ }
140
+ catch ( Exception ex )
141
+ {
142
+ this . Logger . LogError ( "An error occurred while reading a namespace from file '{file}': {ex}" , file . FullName , ex ) ;
143
+ continue ;
144
+ }
145
+ }
146
+ stopwatch . Stop ( ) ;
147
+ this . Logger . LogInformation ( "Completed importing {count} namespaces in {ms} milliseconds" , count , stopwatch . Elapsed . TotalMilliseconds ) ;
148
+ }
149
+
150
+ /// <summary>
151
+ /// Provisions workflows from statis resource files
152
+ /// </summary>
153
+ /// <param name="resources">The <see cref="IResourceRepository"/> used to manage <see cref="IResource"/>s</param>
154
+ /// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
155
+ /// <returns>A new awaitable <see cref="Task"/></returns>
156
+ protected virtual async Task ProvisionWorkflowsAsync ( IResourceRepository resources , CancellationToken cancellationToken )
157
+ {
158
+ var stopwatch = new Stopwatch ( ) ;
159
+ var directory = new DirectoryInfo ( Path . Combine ( this . Options . Seeding . Directory , "workflows" ) ) ;
160
+ if ( ! directory . Exists ) return ;
161
+ this . Logger . LogInformation ( "Starting importing workflows from directory '{directory}'..." , directory . FullName ) ;
162
+ var files = directory . GetFiles ( this . Options . Seeding . FilePattern , SearchOption . AllDirectories ) . Where ( f => f . FullName . EndsWith ( ".json" , StringComparison . OrdinalIgnoreCase ) || f . FullName . EndsWith ( ".yml" , StringComparison . OrdinalIgnoreCase ) || f . FullName . EndsWith ( ".yaml" , StringComparison . OrdinalIgnoreCase ) ) ;
163
+ if ( ! files . Any ( ) )
164
+ {
165
+ this . Logger . LogWarning ( "No workflow static resource files matching search pattern '{pattern}' found in directory '{directory}'. Skipping import." , this . Options . Seeding . FilePattern , directory . FullName ) ;
166
+ return ;
167
+ }
91
168
stopwatch . Restart ( ) ;
169
+ var count = 0 ;
92
170
foreach ( var file in files )
93
171
{
94
172
try
@@ -110,11 +188,6 @@ public virtual async Task StartAsync(CancellationToken cancellationToken)
110
188
Versions = [ workflowDefinition ]
111
189
}
112
190
} ;
113
- if ( await resources . GetAsync < Namespace > ( workflow . GetNamespace ( ) ! , cancellationToken : cancellationToken ) . ConfigureAwait ( false ) == null )
114
- {
115
- await resources . AddAsync ( new Namespace ( ) { Metadata = new ( ) { Name = workflow . GetNamespace ( ) ! } } , false , cancellationToken ) . ConfigureAwait ( false ) ;
116
- this . Logger . LogInformation ( "Successfully created namespace '{namespace}'" , workflow . GetNamespace ( ) ) ;
117
- }
118
191
await resources . AddAsync ( workflow , false , cancellationToken ) . ConfigureAwait ( false ) ;
119
192
}
120
193
else
@@ -138,14 +211,63 @@ public virtual async Task StartAsync(CancellationToken cancellationToken)
138
211
this . Logger . LogInformation ( "Successfully imported workflow '{workflow}' from file '{file}'" , $ "{ workflowDefinition . Document . Name } .{ workflowDefinition . Document . Namespace } :{ workflowDefinition . Document . Version } ", file . FullName ) ;
139
212
count ++ ;
140
213
}
141
- catch ( Exception ex )
214
+ catch ( Exception ex )
142
215
{
143
216
this . Logger . LogError ( "An error occurred while reading a workflow definition from file '{file}': {ex}" , file . FullName , ex ) ;
144
217
continue ;
145
218
}
146
219
}
147
220
stopwatch . Stop ( ) ;
148
- this . Logger . LogInformation ( "Completed importing {count} static resources in {ms} milliseconds" , count , stopwatch . Elapsed . TotalMilliseconds ) ;
221
+ this . Logger . LogInformation ( "Completed importing {count} workflows in {ms} milliseconds" , count , stopwatch . Elapsed . TotalMilliseconds ) ;
222
+ }
223
+
224
+ /// <summary>
225
+ /// Provisions functions from statis resource files
226
+ /// </summary>
227
+ /// <param name="resources">The <see cref="IResourceRepository"/> used to manage <see cref="IResource"/>s</param>
228
+ /// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
229
+ /// <returns>A new awaitable <see cref="Task"/></returns>
230
+ protected virtual async Task ProvisionFunctionsAsync ( IResourceRepository resources , CancellationToken cancellationToken )
231
+ {
232
+ var stopwatch = new Stopwatch ( ) ;
233
+ var directory = new DirectoryInfo ( Path . Combine ( this . Options . Seeding . Directory , "functions" ) ) ;
234
+ if ( ! directory . Exists ) return ;
235
+ this . Logger . LogInformation ( "Starting importing custom functions from directory '{directory}'..." , directory . FullName ) ;
236
+ var files = directory . GetFiles ( this . Options . Seeding . FilePattern , SearchOption . AllDirectories ) . Where ( f => f . FullName . EndsWith ( ".json" , StringComparison . OrdinalIgnoreCase ) || f . FullName . EndsWith ( ".yml" , StringComparison . OrdinalIgnoreCase ) || f . FullName . EndsWith ( ".yaml" , StringComparison . OrdinalIgnoreCase ) ) ;
237
+ if ( ! files . Any ( ) )
238
+ {
239
+ this . Logger . LogWarning ( "No custom function static resource files matching search pattern '{pattern}' found in directory '{directory}'. Skipping import." , this . Options . Seeding . FilePattern , directory . FullName ) ;
240
+ return ;
241
+ }
242
+ stopwatch . Restart ( ) ;
243
+ var count = 0 ;
244
+ foreach ( var file in files )
245
+ {
246
+ try
247
+ {
248
+ var extension = file . FullName . Split ( '.' , StringSplitOptions . RemoveEmptyEntries ) . LastOrDefault ( ) ;
249
+ var serializer = extension ? . ToLowerInvariant ( ) switch
250
+ {
251
+ "json" => ( ITextSerializer ) this . JsonSerializer ,
252
+ "yml" or "yaml" => this . YamlSerializer ,
253
+ _ => throw new NotSupportedException ( $ "The specified extension '{ extension } ' is not supported for static resource files")
254
+ } ;
255
+ using var stream = file . OpenRead ( ) ;
256
+ using var streamReader = new StreamReader ( stream ) ;
257
+ var text = await streamReader . ReadToEndAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
258
+ var func = serializer . Deserialize < CustomFunction > ( text ) ! ;
259
+ await resources . AddAsync ( func , false , cancellationToken ) . ConfigureAwait ( false ) ;
260
+ this . Logger . LogInformation ( "Successfully imported custom function '{customFunction}' from file '{file}'" , func . GetQualifiedName ( ) , file . FullName ) ;
261
+ count ++ ;
262
+ }
263
+ catch ( Exception ex )
264
+ {
265
+ this . Logger . LogError ( "An error occurred while reading a custom function from file '{file}': {ex}" , file . FullName , ex ) ;
266
+ continue ;
267
+ }
268
+ }
269
+ stopwatch . Stop ( ) ;
270
+ this . Logger . LogInformation ( "Completed importing {count} custom functions in {ms} milliseconds" , count , stopwatch . Elapsed . TotalMilliseconds ) ;
149
271
}
150
272
151
273
/// <inheritdoc/>
0 commit comments