From 1b180d9f882036e4c21e6cbf6da34d6fa499e24b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Mayrb=C3=A4url?= Date: Wed, 19 Jan 2022 18:45:34 +0100 Subject: [PATCH 1/5] First draft of Registry service implementation --- AAS API on Azure.sln | 24 +- .../createAzureDigitalTwinsInstance.ps1 | 0 ...eateAzureRedisCacheDBForRegistryServer.ps1 | 0 .../createAzureStorageForAASXFileServer.ps1 | 41 + src/aas-api-models/Interfaces/Registry.cs | 13 +- .../AAS WebApp Registry.csproj | 28 + .../Attributes/ValidateModelStateAttribute.cs | 61 + ...AdministrationShellRegistryInterfaceApi.cs | 259 ++ src/aas-api-webapp-registry/Dockerfile | 22 + .../Filters/BasePathFilter.cs | 51 + .../GeneratePathParamsValidationFilter.cs | 96 + .../IdentifierKeyValuePairModelBinder.cs | 42 + ...entifierKeyValuePairModelBinderProvider.cs | 22 + src/aas-api-webapp-registry/Program.cs | 26 + .../Properties/launchSettings.json | 37 + src/aas-api-webapp-registry/Startup.cs | 171 + .../appsettings.Development.json | 9 + src/aas-api-webapp-registry/appsettings.json | 18 + src/aas-api-webapp-registry/web.config | 12 + src/aas-api-webapp-registry/wwwroot/README.md | 42 + .../wwwroot/index.html | 1 + .../wwwroot/swagger-original-file.json | 252 ++ .../wwwroot/swagger-original.json | 3576 +++++++++++++++++ .../wwwroot/web.config | 9 + .../AAS Registry Service Tests.csproj | 32 + .../AASRegistryServiceTests.cs | 121 + .../appsettings.tests.json | 10 + .../AAS Registry Service.csproj | 18 + src/aas-registry-service/AASRegistry.cs | 39 + src/aas-registry-service/Cache.cs | 38 + src/aas-registry-service/Program.cs | 12 + .../RedisImpl/RedisAASRegistry.cs | 176 + 32 files changed, 5253 insertions(+), 5 deletions(-) create mode 100644 scripts/azuredeployment/createAzureDigitalTwinsInstance.ps1 create mode 100644 scripts/azuredeployment/createAzureRedisCacheDBForRegistryServer.ps1 create mode 100644 scripts/azuredeployment/createAzureStorageForAASXFileServer.ps1 create mode 100644 src/aas-api-webapp-registry/AAS WebApp Registry.csproj create mode 100644 src/aas-api-webapp-registry/Attributes/ValidateModelStateAttribute.cs create mode 100644 src/aas-api-webapp-registry/Controllers/AssetAdministrationShellRegistryInterfaceApi.cs create mode 100644 src/aas-api-webapp-registry/Dockerfile create mode 100644 src/aas-api-webapp-registry/Filters/BasePathFilter.cs create mode 100644 src/aas-api-webapp-registry/Filters/GeneratePathParamsValidationFilter.cs create mode 100644 src/aas-api-webapp-registry/Models/IdentifierKeyValuePairModelBinder.cs create mode 100644 src/aas-api-webapp-registry/Models/IdentifierKeyValuePairModelBinderProvider.cs create mode 100644 src/aas-api-webapp-registry/Program.cs create mode 100644 src/aas-api-webapp-registry/Properties/launchSettings.json create mode 100644 src/aas-api-webapp-registry/Startup.cs create mode 100644 src/aas-api-webapp-registry/appsettings.Development.json create mode 100644 src/aas-api-webapp-registry/appsettings.json create mode 100644 src/aas-api-webapp-registry/web.config create mode 100644 src/aas-api-webapp-registry/wwwroot/README.md create mode 100644 src/aas-api-webapp-registry/wwwroot/index.html create mode 100644 src/aas-api-webapp-registry/wwwroot/swagger-original-file.json create mode 100644 src/aas-api-webapp-registry/wwwroot/swagger-original.json create mode 100644 src/aas-api-webapp-registry/wwwroot/web.config create mode 100644 src/aas-registry-service-tests/AAS Registry Service Tests.csproj create mode 100644 src/aas-registry-service-tests/AASRegistryServiceTests.cs create mode 100644 src/aas-registry-service-tests/appsettings.tests.json create mode 100644 src/aas-registry-service/AAS Registry Service.csproj create mode 100644 src/aas-registry-service/AASRegistry.cs create mode 100644 src/aas-registry-service/Cache.cs create mode 100644 src/aas-registry-service/Program.cs create mode 100644 src/aas-registry-service/RedisImpl/RedisAASRegistry.cs diff --git a/AAS API on Azure.sln b/AAS API on Azure.sln index 96c28b1..994f86e 100644 --- a/AAS API on Azure.sln +++ b/AAS API on Azure.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31911.196 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32112.339 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AAS WebApp Repository", "src\aas-api-webapp-repository\AAS WebApp Repository.csproj", "{37E281C2-FC84-4BD9-AFB5-AC55A9D92B0F}" ProjectSection(ProjectDependencies) = postProject @@ -32,7 +32,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AAS AASX File Service", "sr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AAS AASX File Service Tests", "src\aas-aasxfile-service-tests\AAS AASX File Service Tests.csproj", "{82A9C310-4E09-4FD7-B3DB-F9E6C670A536}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AAS WebApp Discovery", "src\aas-api-webapp-discovery\AAS WebApp Discovery.csproj", "{F54DA57A-DC54-4F84-A0C7-0ED10B60C226}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AAS WebApp Discovery", "src\aas-api-webapp-discovery\AAS WebApp Discovery.csproj", "{F54DA57A-DC54-4F84-A0C7-0ED10B60C226}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AAS Registry Service", "src\aas-registry-service\AAS Registry Service.csproj", "{0D0AD4E2-C76D-43F7-B62E-6D386DF2A93E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AAS Registry Service Tests", "src\aas-registry-service-tests\AAS Registry Service Tests.csproj", "{A4F5E0BC-2400-422E-9D38-6B82BB3E1347}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AAS WebApp Registry", "src\aas-api-webapp-registry\AAS WebApp Registry.csproj", "{B3AA9231-9B5E-4A71-AE05-57A687CD9097}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -84,6 +90,18 @@ Global {F54DA57A-DC54-4F84-A0C7-0ED10B60C226}.Debug|Any CPU.Build.0 = Debug|Any CPU {F54DA57A-DC54-4F84-A0C7-0ED10B60C226}.Release|Any CPU.ActiveCfg = Release|Any CPU {F54DA57A-DC54-4F84-A0C7-0ED10B60C226}.Release|Any CPU.Build.0 = Release|Any CPU + {0D0AD4E2-C76D-43F7-B62E-6D386DF2A93E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D0AD4E2-C76D-43F7-B62E-6D386DF2A93E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D0AD4E2-C76D-43F7-B62E-6D386DF2A93E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0D0AD4E2-C76D-43F7-B62E-6D386DF2A93E}.Release|Any CPU.Build.0 = Release|Any CPU + {A4F5E0BC-2400-422E-9D38-6B82BB3E1347}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4F5E0BC-2400-422E-9D38-6B82BB3E1347}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4F5E0BC-2400-422E-9D38-6B82BB3E1347}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4F5E0BC-2400-422E-9D38-6B82BB3E1347}.Release|Any CPU.Build.0 = Release|Any CPU + {B3AA9231-9B5E-4A71-AE05-57A687CD9097}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B3AA9231-9B5E-4A71-AE05-57A687CD9097}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B3AA9231-9B5E-4A71-AE05-57A687CD9097}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B3AA9231-9B5E-4A71-AE05-57A687CD9097}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/scripts/azuredeployment/createAzureDigitalTwinsInstance.ps1 b/scripts/azuredeployment/createAzureDigitalTwinsInstance.ps1 new file mode 100644 index 0000000..e69de29 diff --git a/scripts/azuredeployment/createAzureRedisCacheDBForRegistryServer.ps1 b/scripts/azuredeployment/createAzureRedisCacheDBForRegistryServer.ps1 new file mode 100644 index 0000000..e69de29 diff --git a/scripts/azuredeployment/createAzureStorageForAASXFileServer.ps1 b/scripts/azuredeployment/createAzureStorageForAASXFileServer.ps1 new file mode 100644 index 0000000..585e886 --- /dev/null +++ b/scripts/azuredeployment/createAzureStorageForAASXFileServer.ps1 @@ -0,0 +1,41 @@ +# Create Azure storage account for an AASX File Server +# uses the currently selected Azure subscription. Azure CLI version 2.14.0 or higher. +# Will create an ADLS storage account with hierarchical namespaces + +param ( + [string] $dcloc = "West Europe", + [string] $rg = "aas-sample-rg", + [string] $storageAccName = "aasxfileserverstorage", + [string] $storageSku = "Standard_LRS" +) + +# 1. Check existenc of resource group and create it if it's not existing yet +$rsgExists = az group exists -n $rg +if ( $rsgExists -eq 'false') { + Write-Host "Resource group " $rg " doesn't exist. Creating it now." + az group create -l $dcloc -n $rg +} + +# 2. Create Azure storage account if not exists yet +$storageAccExists = $(az storage account check-name --name $storageAccName) | ConvertFrom-Json +if ( $storageAccExists.nameAvailable ) { + Write-Host "Storage account " $storageAccName " doesn't exist. Creating it now." + az storage account create -n $storageAccName -g $rg -l $dcloc --access-tier Hot --sku $storageSku --kind "StorageV2" ` + --allow-blob-public-access false --allow-shared-key-access false --enable-hierarchical-namespace true +} + +# 3. Create container for AASX file packages +$contExists = $(az storage container exists --name aasxfiles --account-name $storageAccName --auth-mode login) | ConvertFrom-Json +if (-not($contExists.exists) ) { + Write-Host "Container for AASX packages doesn't exist. Creating it now." + az storage container create --name aasxfiles --account-name $storageAccName --auth-mode login +} + +# 4. Assign roles to creating user +$currAcc = $(az account show) | ConvertFrom-Json +$storageAccId = $(az storage account show -n $storageAccName -g $rg --query id) +az role assignment create --role "Storage Blob Data Owner" --assignee $currAcc.user.name --scope $storageAccId + +# 5. Set correct access +# $oldContAcc = $(az storage fs access show --account-name $storageAccName --file-system aasxfiles --path . --auth-mode login) +az storage fs access set --acl "user::rwx,group::r-x,other::r-x" --path . -f aasxfiles --account-name $storageAccName --auth-mode login diff --git a/src/aas-api-models/Interfaces/Registry.cs b/src/aas-api-models/Interfaces/Registry.cs index 1b04684..1f62847 100644 --- a/src/aas-api-models/Interfaces/Registry.cs +++ b/src/aas-api-models/Interfaces/Registry.cs @@ -1,10 +1,19 @@ -using System; +using AAS.API.Models; using System.Collections.Generic; -using System.Text; +using System.Threading.Tasks; namespace AAS.API.Interfaces { public interface Registry { + public Task> GetAllAssetAdministrationShellDescriptors(); + + public Task GetAssetAdministrationShellDescriptorById(string aasIdentifier); + + public Task CreateAssetAdministrationShellDescriptor(AssetAdministrationShellDescriptor aasDesc); + + public Task UpdateAssetAdministrationShellDescriptorById(AssetAdministrationShellDescriptor aasDesc, string aasIdentifier); + + public Task DeleteAssetAdministrationShellDescriptorById(string aasIdentifier); } } diff --git a/src/aas-api-webapp-registry/AAS WebApp Registry.csproj b/src/aas-api-webapp-registry/AAS WebApp Registry.csproj new file mode 100644 index 0000000..8c86d08 --- /dev/null +++ b/src/aas-api-webapp-registry/AAS WebApp Registry.csproj @@ -0,0 +1,28 @@ + + + + netcoreapp3.1 + AAS_WebApp_Registry + c7576273-fad3-4cb6-8ad2-9e25bafa9106 + Linux + ..\.. + + + + + + + + + + + + + + + + + + + + diff --git a/src/aas-api-webapp-registry/Attributes/ValidateModelStateAttribute.cs b/src/aas-api-webapp-registry/Attributes/ValidateModelStateAttribute.cs new file mode 100644 index 0000000..f0e0900 --- /dev/null +++ b/src/aas-api-webapp-registry/Attributes/ValidateModelStateAttribute.cs @@ -0,0 +1,61 @@ +using System.ComponentModel.DataAnnotations; +using System.Reflection; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace AAS.API.Attributes +{ + /// + /// Model state validation attribute + /// + public class ValidateModelStateAttribute : ActionFilterAttribute + { + /// + /// Called before the action method is invoked + /// + /// + public override void OnActionExecuting(ActionExecutingContext context) + { + // Per https://blog.markvincze.com/how-to-validate-action-parameters-with-dataannotation-attributes/ + var descriptor = context.ActionDescriptor as ControllerActionDescriptor; + if (descriptor != null) + { + foreach (var parameter in descriptor.MethodInfo.GetParameters()) + { + object args = null; + if (context.ActionArguments.ContainsKey(parameter.Name)) + { + args = context.ActionArguments[parameter.Name]; + } + + ValidateAttributes(parameter, args, context.ModelState); + } + } + + if (!context.ModelState.IsValid) + { + context.Result = new BadRequestObjectResult(context.ModelState); + } + } + + private void ValidateAttributes(ParameterInfo parameter, object args, ModelStateDictionary modelState) + { + foreach (var attributeData in parameter.CustomAttributes) + { + var attributeInstance = parameter.GetCustomAttribute(attributeData.AttributeType); + + var validationAttribute = attributeInstance as ValidationAttribute; + if (validationAttribute != null) + { + var isValid = validationAttribute.IsValid(args); + if (!isValid) + { + modelState.AddModelError(parameter.Name, validationAttribute.FormatErrorMessage(parameter.Name)); + } + } + } + } + } +} diff --git a/src/aas-api-webapp-registry/Controllers/AssetAdministrationShellRegistryInterfaceApi.cs b/src/aas-api-webapp-registry/Controllers/AssetAdministrationShellRegistryInterfaceApi.cs new file mode 100644 index 0000000..d7c0465 --- /dev/null +++ b/src/aas-api-webapp-registry/Controllers/AssetAdministrationShellRegistryInterfaceApi.cs @@ -0,0 +1,259 @@ +/* + * DotAAS Part 2 | HTTP/REST | Entire Interface Collection + * + * The entire interface collection as part of Details of the Asset Administration Shell Part 2 + * + * OpenAPI spec version: Final-Draft + * + * Generated by: https://github.com/swagger-api/swagger-codegen.git + */ +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; +using Swashbuckle.AspNetCore.SwaggerGen; +using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using AAS.API.Attributes; + +using Microsoft.AspNetCore.Authorization; +using AAS.API.Models; +using Microsoft.Extensions.Logging; +using AAS.API.Registry; +using System.Web; + +namespace AAS.API.WebApp.Controllers +{ + /// + /// + /// + [Authorize] + [ApiController] + public class AssetAdministrationShellRegistryInterfaceApiController : ControllerBase + { + private readonly ILogger _logger; + + private AASRegistry registryService; + + public AssetAdministrationShellRegistryInterfaceApiController(ILogger log, AASRegistry registry) + { + _logger = log; + registryService = registry; + } + + /// + /// Deletes an Asset Administration Shell Descriptor, i.e. de-registers an AAS + /// + /// The Asset Administration Shell’s unique id (BASE64-URL-encoded) + /// Asset Administration Shell Descriptor deleted successfully + [HttpDelete] + [Route("api/v1/registry/shell-descriptors/{aasIdentifier}")] + [ValidateModelState] + [SwaggerOperation("DeleteAssetAdministrationShellDescriptorById")] + public virtual IActionResult DeleteAssetAdministrationShellDescriptorById([FromRoute][Required]string aasIdentifier) + { + //TODO: Uncomment the next line to return response 204 or use other options such as return this.NotFound(), return this.BadRequest(..), ... + // return StatusCode(204); + + throw new NotImplementedException(); + } + + /// + /// Deletes a Submodel Descriptor, i.e. de-registers a submodel + /// + /// The Asset Administration Shell’s unique id (BASE64-URL-encoded) + /// The Submodel’s unique id (BASE64-URL-encoded) + /// Submodel Descriptor deleted successfully + [HttpDelete] + [Route("api/v1/registry/shell-descriptors/{aasIdentifier}/submodel-descriptors/{submodelIdentifier}")] + [ValidateModelState] + [SwaggerOperation("DeleteSubmodelDescriptorByIdAASRegistry")] + public virtual IActionResult DeleteSubmodelDescriptorByIdAASRegistry([FromRoute][Required]string aasIdentifier, [FromRoute][Required]string submodelIdentifier) + { + //TODO: Uncomment the next line to return response 204 or use other options such as return this.NotFound(), return this.BadRequest(..), ... + // return StatusCode(204); + + throw new NotImplementedException(); + } + + /// + /// Returns all Asset Administration Shell Descriptors + /// + /// Requested Asset Administration Shell Descriptors + [HttpGet] + [Route("api/v1/registry/shell-descriptors")] + [ValidateModelState] + [SwaggerOperation("GetAllAssetAdministrationShellDescriptors")] + [SwaggerResponse(statusCode: 200, type: typeof(List), description: "Requested Asset Administration Shell Descriptors")] + public virtual IActionResult GetAllAssetAdministrationShellDescriptors() + { + //TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ... + // return StatusCode(200, default(List)); + string exampleJson = null; + exampleJson = "[ {\n \"identification\" : \"identification\",\n \"idShort\" : \"idShort\",\n \"specificAssetIds\" : [ \"\", \"\" ],\n \"administration\" : {\n \"version\" : \"version\",\n \"revision\" : \"revision\"\n },\n \"description\" : [ {\n \"language\" : \"language\",\n \"text\" : \"text\"\n }, {\n \"language\" : \"language\",\n \"text\" : \"text\"\n } ],\n \"submodelDescriptors\" : [ {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n }, {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n } ],\n \"globalAssetId\" : \"\"\n}, {\n \"identification\" : \"identification\",\n \"idShort\" : \"idShort\",\n \"specificAssetIds\" : [ \"\", \"\" ],\n \"administration\" : {\n \"version\" : \"version\",\n \"revision\" : \"revision\"\n },\n \"description\" : [ {\n \"language\" : \"language\",\n \"text\" : \"text\"\n }, {\n \"language\" : \"language\",\n \"text\" : \"text\"\n } ],\n \"submodelDescriptors\" : [ {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n }, {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n } ],\n \"globalAssetId\" : \"\"\n} ]"; + + var example = exampleJson != null + ? JsonConvert.DeserializeObject>(exampleJson) + : default(List); //TODO: Change the data returned + return new ObjectResult(example); + } + + /// + /// Returns all Submodel Descriptors + /// + /// The Asset Administration Shell’s unique id (BASE64-URL-encoded) + /// Requested Submodel Descriptors + [HttpGet] + [Route("api/v1/registry/shell-descriptors/{aasIdentifier}/submodel-descriptors")] + [ValidateModelState] + [SwaggerOperation("GetAllSubmodelDescriptorsAASRegistry")] + [SwaggerResponse(statusCode: 200, type: typeof(List), description: "Requested Submodel Descriptors")] + public virtual IActionResult GetAllSubmodelDescriptorsAASRegistry([FromRoute][Required]string aasIdentifier) + { + //TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ... + // return StatusCode(200, default(List)); + string exampleJson = null; + exampleJson = "[ {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n}, {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n} ]"; + + var example = exampleJson != null + ? JsonConvert.DeserializeObject>(exampleJson) + : default(List); //TODO: Change the data returned + return new ObjectResult(example); + } + + /// + /// Returns a specific Asset Administration Shell Descriptor + /// + /// The Asset Administration Shell’s unique id (BASE64-URL-encoded) + /// Requested Asset Administration Shell Descriptor + [HttpGet] + [Route("api/v1/registry/shell-descriptors/{aasIdentifier}")] + [ValidateModelState] + [SwaggerOperation("GetAssetAdministrationShellDescriptorById")] + [SwaggerResponse(statusCode: 200, type: typeof(AssetAdministrationShellDescriptor), description: "Requested Asset Administration Shell Descriptor")] + public virtual IActionResult GetAssetAdministrationShellDescriptorById([FromRoute][Required]string aasIdentifier) + { + //TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ... + // return StatusCode(200, default(AssetAdministrationShellDescriptor)); + /* + string exampleJson = null; + exampleJson = "{\n \"identification\" : \"identification\",\n \"idShort\" : \"idShort\",\n \"specificAssetIds\" : [ \"\", \"\" ],\n \"administration\" : {\n \"version\" : \"version\",\n \"revision\" : \"revision\"\n },\n \"description\" : [ {\n \"language\" : \"language\",\n \"text\" : \"text\"\n }, {\n \"language\" : \"language\",\n \"text\" : \"text\"\n } ],\n \"submodelDescriptors\" : [ {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n }, {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n } ],\n \"globalAssetId\" : \"\"\n}"; + + var example = exampleJson != null + ? JsonConvert.DeserializeObject(exampleJson) + : default(AssetAdministrationShellDescriptor); //TODO: Change the data returned + return new ObjectResult(example); + */ + + return new ObjectResult(registryService.GetAssetAdministrationShellDescriptorById(HttpUtility.UrlDecode(aasIdentifier)).GetAwaiter().GetResult()); + } + + /// + /// Returns a specific Submodel Descriptor + /// + /// The Asset Administration Shell’s unique id (BASE64-URL-encoded) + /// The Submodel’s unique id (BASE64-URL-encoded) + /// Requested Submodel Descriptor + [HttpGet] + [Route("api/v1/registry/shell-descriptors/{aasIdentifier}/submodel-descriptors/{submodelIdentifier}")] + [ValidateModelState] + [SwaggerOperation("GetSubmodelDescriptorByIdAASRegistry")] + [SwaggerResponse(statusCode: 200, type: typeof(SubmodelDescriptor), description: "Requested Submodel Descriptor")] + public virtual IActionResult GetSubmodelDescriptorByIdAASRegistry([FromRoute][Required]string aasIdentifier, [FromRoute][Required]string submodelIdentifier) + { + //TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ... + // return StatusCode(200, default(SubmodelDescriptor)); + string exampleJson = null; + exampleJson = "{\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n}"; + + var example = exampleJson != null + ? JsonConvert.DeserializeObject(exampleJson) + : default(SubmodelDescriptor); //TODO: Change the data returned + return new ObjectResult(example); + } + + /// + /// Creates a new Asset Administration Shell Descriptor, i.e. registers an AAS + /// + /// Asset Administration Shell Descriptor object + /// Asset Administration Shell Descriptor created successfully + [HttpPost] + [Route("api/v1/registry/shell-descriptors")] + [ValidateModelState] + [SwaggerOperation("PostAssetAdministrationShellDescriptor")] + [SwaggerResponse(statusCode: 201, type: typeof(AssetAdministrationShellDescriptor), description: "Asset Administration Shell Descriptor created successfully")] + public virtual IActionResult PostAssetAdministrationShellDescriptor([FromBody]AssetAdministrationShellDescriptor body) + { + //TODO: Uncomment the next line to return response 201 or use other options such as return this.NotFound(), return this.BadRequest(..), ... + // return StatusCode(201, default(AssetAdministrationShellDescriptor)); + string exampleJson = null; + exampleJson = "{\n \"identification\" : \"identification\",\n \"idShort\" : \"idShort\",\n \"specificAssetIds\" : [ \"\", \"\" ],\n \"administration\" : {\n \"version\" : \"version\",\n \"revision\" : \"revision\"\n },\n \"description\" : [ {\n \"language\" : \"language\",\n \"text\" : \"text\"\n }, {\n \"language\" : \"language\",\n \"text\" : \"text\"\n } ],\n \"submodelDescriptors\" : [ {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n }, {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n } ],\n \"globalAssetId\" : \"\"\n}"; + + var example = exampleJson != null + ? JsonConvert.DeserializeObject(exampleJson) + : default(AssetAdministrationShellDescriptor); //TODO: Change the data returned + return new ObjectResult(example); + } + + /// + /// Creates a new Submodel Descriptor, i.e. registers a submodel + /// + /// Submodel Descriptor object + /// The Asset Administration Shell’s unique id (BASE64-URL-encoded) + /// Submodel Descriptor created successfully + [HttpPost] + [Route("api/v1/registry/shell-descriptors/{aasIdentifier}/submodel-descriptors")] + [ValidateModelState] + [SwaggerOperation("PostSubmodelDescriptorAASRegistry")] + [SwaggerResponse(statusCode: 201, type: typeof(SubmodelDescriptor), description: "Submodel Descriptor created successfully")] + public virtual IActionResult PostSubmodelDescriptorAASRegistry([FromBody]SubmodelDescriptor body, [FromRoute][Required]string aasIdentifier) + { + //TODO: Uncomment the next line to return response 201 or use other options such as return this.NotFound(), return this.BadRequest(..), ... + // return StatusCode(201, default(SubmodelDescriptor)); + string exampleJson = null; + exampleJson = "{\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n}"; + + var example = exampleJson != null + ? JsonConvert.DeserializeObject(exampleJson) + : default(SubmodelDescriptor); //TODO: Change the data returned + return new ObjectResult(example); + } + + /// + /// Updates an existing Asset Administration Shell Descriptor + /// + /// Asset Administration Shell Descriptor object + /// The Asset Administration Shell’s unique id (BASE64-URL-encoded) + /// Asset Administration Shell Descriptor updated successfully + [HttpPut] + [Route("api/v1/registry/shell-descriptors/{aasIdentifier}")] + [ValidateModelState] + [SwaggerOperation("PutAssetAdministrationShellDescriptorById")] + public virtual IActionResult PutAssetAdministrationShellDescriptorById([FromBody]AssetAdministrationShellDescriptor body, [FromRoute][Required]string aasIdentifier) + { + //TODO: Uncomment the next line to return response 204 or use other options such as return this.NotFound(), return this.BadRequest(..), ... + // return StatusCode(204); + + throw new NotImplementedException(); + } + + /// + /// Updates an existing Submodel Descriptor + /// + /// Submodel Descriptor object + /// The Asset Administration Shell’s unique id (BASE64-URL-encoded) + /// The Submodel’s unique id (BASE64-URL-encoded) + /// Submodel Descriptor updated successfully + [HttpPut] + [Route("api/v1/registry/shell-descriptors/{aasIdentifier}/submodel-descriptors/{submodelIdentifier}")] + [ValidateModelState] + [SwaggerOperation("PutSubmodelDescriptorByIdAASRegistry")] + public virtual IActionResult PutSubmodelDescriptorByIdAASRegistry([FromBody]SubmodelDescriptor body, [FromRoute][Required]string aasIdentifier, [FromRoute][Required]string submodelIdentifier) + { + //TODO: Uncomment the next line to return response 204 or use other options such as return this.NotFound(), return this.BadRequest(..), ... + // return StatusCode(204); + + throw new NotImplementedException(); + } + } +} diff --git a/src/aas-api-webapp-registry/Dockerfile b/src/aas-api-webapp-registry/Dockerfile new file mode 100644 index 0000000..68a8479 --- /dev/null +++ b/src/aas-api-webapp-registry/Dockerfile @@ -0,0 +1,22 @@ +#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/aspnet:3.1 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +FROM mcr.microsoft.com/dotnet/sdk:3.1 AS build +WORKDIR /src +COPY ["src/aas-api-webapp-registry/AAS WebApp Registry.csproj", "src/AAS WebApp Registry/"] +RUN dotnet restore "src/aas-api-webapp-registry/AAS WebApp Registry.csproj" +COPY . . +WORKDIR "/src/src/aas-api-webapp-registry" +RUN dotnet build "AAS WebApp Registry.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "AAS WebApp Registry.csproj" -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "AAS WebApp Registry.dll"] \ No newline at end of file diff --git a/src/aas-api-webapp-registry/Filters/BasePathFilter.cs b/src/aas-api-webapp-registry/Filters/BasePathFilter.cs new file mode 100644 index 0000000..47825ee --- /dev/null +++ b/src/aas-api-webapp-registry/Filters/BasePathFilter.cs @@ -0,0 +1,51 @@ +using System.Linq; +using System.Text.RegularExpressions; +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; +using Microsoft.OpenApi.Models; + +namespace AAS.API.WebApp.Filters +{ + /// + /// BasePath Document Filter sets BasePath property of Swagger and removes it from the individual URL paths + /// + public class BasePathFilter : IDocumentFilter + { + /// + /// Constructor + /// + /// BasePath to remove from Operations + public BasePathFilter(string basePath) + { + BasePath = basePath; + } + + /// + /// Gets the BasePath of the Swagger Doc + /// + /// The BasePath of the Swagger Doc + public string BasePath { get; } + + /// + /// Apply the filter + /// + /// OpenApiDocument + /// FilterContext + public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) + { + swaggerDoc.Servers.Add(new OpenApiServer() { Url = this.BasePath }); + + var pathsToModify = swaggerDoc.Paths.Where(p => p.Key.StartsWith(this.BasePath)).ToList(); + + foreach (var path in pathsToModify) + { + if (path.Key.StartsWith(this.BasePath)) + { + string newKey = Regex.Replace(path.Key, $"^{this.BasePath}", string.Empty); + swaggerDoc.Paths.Remove(path.Key); + swaggerDoc.Paths.Add(newKey, path.Value); + } + } + } + } +} diff --git a/src/aas-api-webapp-registry/Filters/GeneratePathParamsValidationFilter.cs b/src/aas-api-webapp-registry/Filters/GeneratePathParamsValidationFilter.cs new file mode 100644 index 0000000..e5272fc --- /dev/null +++ b/src/aas-api-webapp-registry/Filters/GeneratePathParamsValidationFilter.cs @@ -0,0 +1,96 @@ +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace AAS.API.WebApp.Filters +{ + /// + /// Path Parameter Validation Rules Filter + /// + public class GeneratePathParamsValidationFilter : IOperationFilter + { + /// + /// Constructor + /// + /// Operation + /// OperationFilterContext + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + var pars = context.ApiDescription.ParameterDescriptions; + + foreach (var par in pars) + { + var swaggerParam = operation.Parameters.SingleOrDefault(p => p.Name == par.Name); + + var attributes = ((ControllerParameterDescriptor)par.ParameterDescriptor).ParameterInfo.CustomAttributes; + + if (attributes != null && attributes.Count() > 0 && swaggerParam != null) + { + // Required - [Required] + var requiredAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(RequiredAttribute)); + if (requiredAttr != null) + { + swaggerParam.Required = true; + } + + // Regex Pattern [RegularExpression] + var regexAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(RegularExpressionAttribute)); + if (regexAttr != null) + { + string regex = (string)regexAttr.ConstructorArguments[0].Value; + if (swaggerParam is OpenApiParameter) + { + ((OpenApiParameter)swaggerParam).Schema.Pattern = regex; + } + } + + // String Length [StringLength] + int? minLenght = null, maxLength = null; + var stringLengthAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(StringLengthAttribute)); + if (stringLengthAttr != null) + { + if (stringLengthAttr.NamedArguments.Count == 1) + { + minLenght = (int)stringLengthAttr.NamedArguments.Single(p => p.MemberName == "MinimumLength").TypedValue.Value; + } + maxLength = (int)stringLengthAttr.ConstructorArguments[0].Value; + } + + var minLengthAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(MinLengthAttribute)); + if (minLengthAttr != null) + { + minLenght = (int)minLengthAttr.ConstructorArguments[0].Value; + } + + var maxLengthAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(MaxLengthAttribute)); + if (maxLengthAttr != null) + { + maxLength = (int)maxLengthAttr.ConstructorArguments[0].Value; + } + + if (swaggerParam is OpenApiParameter) + { + ((OpenApiParameter)swaggerParam).Schema.MinLength = minLenght; + ((OpenApiParameter)swaggerParam).Schema.MaxLength = maxLength; + } + + // Range [Range] + var rangeAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(RangeAttribute)); + if (rangeAttr != null) + { + int rangeMin = (int)rangeAttr.ConstructorArguments[0].Value; + int rangeMax = (int)rangeAttr.ConstructorArguments[1].Value; + + if (swaggerParam is OpenApiParameter) + { + ((OpenApiParameter)swaggerParam).Schema.Minimum = rangeMin; + ((OpenApiParameter)swaggerParam).Schema.Maximum = rangeMax; + } + } + } + } + } + } +} diff --git a/src/aas-api-webapp-registry/Models/IdentifierKeyValuePairModelBinder.cs b/src/aas-api-webapp-registry/Models/IdentifierKeyValuePairModelBinder.cs new file mode 100644 index 0000000..fab61b8 --- /dev/null +++ b/src/aas-api-webapp-registry/Models/IdentifierKeyValuePairModelBinder.cs @@ -0,0 +1,42 @@ +using AAS.API.Models; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace AAS.API.Registry.Models +{ + /// + /// + /// + public class IdentifierKeyValuePairModelBinder : IModelBinder + { + /// + /// + /// + public Task BindModelAsync(ModelBindingContext bindingContext) + { + // Specify a default argument name if none is set by ModelBinderAttribute + var modelName = bindingContext.ModelName; + if (String.IsNullOrEmpty(modelName)) + { + modelName = "model"; + } + + // Try to fetch the value of the argument by name + var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName); + if (valueProviderResult == ValueProviderResult.None) + { + return Task.CompletedTask; + } + + IdentifierKeyValuePair result = JsonConvert.DeserializeObject(valueProviderResult.FirstValue); + bindingContext.Result = ModelBindingResult.Success(result); + + return Task.CompletedTask; + } + } +} diff --git a/src/aas-api-webapp-registry/Models/IdentifierKeyValuePairModelBinderProvider.cs b/src/aas-api-webapp-registry/Models/IdentifierKeyValuePairModelBinderProvider.cs new file mode 100644 index 0000000..7e17cbd --- /dev/null +++ b/src/aas-api-webapp-registry/Models/IdentifierKeyValuePairModelBinderProvider.cs @@ -0,0 +1,22 @@ +using AAS.API.Models; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace AAS.API.Registry.Models +{ + /// + /// + /// + public class IdentifierKeyValuePairModelBinderProvider : IModelBinderProvider + { + /// + /// + /// + public IModelBinder GetBinder(ModelBinderProviderContext context) + { + if (context.Metadata.ModelType == typeof(IdentifierKeyValuePair)) + return new IdentifierKeyValuePairModelBinder(); + + return null; + } + } +} diff --git a/src/aas-api-webapp-registry/Program.cs b/src/aas-api-webapp-registry/Program.cs new file mode 100644 index 0000000..ae409eb --- /dev/null +++ b/src/aas-api-webapp-registry/Program.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace AAS.API.Registry.Server +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/src/aas-api-webapp-registry/Properties/launchSettings.json b/src/aas-api-webapp-registry/Properties/launchSettings.json new file mode 100644 index 0000000..dba385f --- /dev/null +++ b/src/aas-api-webapp-registry/Properties/launchSettings.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:41891", + "sslPort": 44362 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "AAS_WebApp_Registry": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + }, + "Docker": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", + "publishAllPorts": true, + "useSSL": true + } + } +} \ No newline at end of file diff --git a/src/aas-api-webapp-registry/Startup.cs b/src/aas-api-webapp-registry/Startup.cs new file mode 100644 index 0000000..0ec87c6 --- /dev/null +++ b/src/aas-api-webapp-registry/Startup.cs @@ -0,0 +1,171 @@ +using AAS.API.Registry.Models; +using AAS.API.WebApp.Filters; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Identity.Web; +using Microsoft.OpenApi.Models; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; +using System; + +namespace AAS.API.Registry.Server +{ + /// + /// Startup + /// + public class Startup + { + private readonly IWebHostEnvironment _hostingEnv; + + private IConfiguration Configuration { get; } + + /// + /// Constructor + /// + /// + /// + public Startup(IWebHostEnvironment env, IConfiguration configuration) + { + _hostingEnv = env; + Configuration = configuration; + } + + /// + /// This method gets called by the runtime. Use this method to add services to the container. + /// + /// + public void ConfigureServices(IServiceCollection services) + { + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd")); + + // Add framework services. + services + .AddMvc(options => + { + options.InputFormatters.RemoveType(); + options.OutputFormatters.RemoveType(); + options.ModelBinderProviders.Insert(0, new IdentifierKeyValuePairModelBinderProvider()); + }) + .AddNewtonsoftJson(opts => + { + opts.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + opts.SerializerSettings.Converters.Add(new StringEnumConverter(new CamelCaseNamingStrategy())); + }) + .AddXmlSerializerFormatters(); + + + services + .AddSwaggerGen(c => + { + c.SwaggerDoc("Final-Draft", new OpenApiInfo + { + Version = "Final-Draft", + Title = "DotAAS Part 2 | HTTP/REST | Entire Interface Collection", + Description = "DotAAS Part 2 | HTTP/REST | Entire Interface Collection (ASP.NET Core 3.1)", + Contact = new OpenApiContact() + { + Name = "Michael Hoffmeister, Torben Miny, Andreas Orzelski, Manuel Sauer, Constantin Ziesche", + Url = new Uri("https://github.com/swagger-api/swagger-codegen"), + Email = "" + }, + TermsOfService = new Uri("https://github.com/admin-shell-io/aas-specs") + }); + c.CustomSchemaIds(type => type.FullName); + //c.IncludeXmlComments($"{AppContext.BaseDirectory}{Path.DirectorySeparatorChar}{_hostingEnv.ApplicationName}.xml"); + + // Include DataAnnotation attributes on Controller Action parameters as Swagger validation rules (e.g required, pattern, ..) + // Use [ValidateModelState] on Actions to actually validate it in C# as well! + c.OperationFilter(); + + c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + Description = "JWT Authorization header using the Bearer scheme (Example: 'Bearer 12345abcdef')", + Name = "Authorization", + In = ParameterLocation.Header, + Type = SecuritySchemeType.ApiKey, + Scheme = "Bearer" + }); + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + Array.Empty() + } + }); + }); + + services.AddStackExchangeRedisCache(setupAction => + { + setupAction.Configuration = Configuration.GetConnectionString("RedisCache"); + }); + services.AddSingleton(); + } + + /// + /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + /// + /// + /// + /// + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) + { + app.UseRouting(); + + //TODO: Uncomment this if you need wwwroot folder + //app.UseStaticFiles(); + + app.UseAuthentication(); + app.UseAuthorization(); + + app.UseSwagger(options => + { + options.SerializeAsV2 = Configuration.GetValue("OPENAPI_JSON_VERSION_2"); + }); + app.UseSwaggerUI(c => + { + //TODO: Either use the SwaggerGen generated Swagger contract (generated from C# classes) + c.SwaggerEndpoint("/swagger/Final-Draft/swagger.json", "Asset Administration Shell Part 2 | HTTP/REST | Discovery Interface"); + + //TODO: Or alternatively use the original Swagger contract that's included in the static files - Dont forget to uncomment app.UseStaticFiles + //c.SwaggerEndpoint("/swagger-original-discovery.json", "Asset Administration Shell Part 2 | HTTP/REST | Discovery Interface"); + }); + + //TODO: Use Https Redirection + // app.UseHttpsRedirection(); + + app.UseEndpoints(endpoints => + { + if (env.IsDevelopment()) + endpoints.MapControllers().WithMetadata(new AllowAnonymousAttribute()); + else + endpoints.MapControllers(); + }); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + //TODO: Enable production exception handling (https://docs.microsoft.com/en-us/aspnet/core/fundamentals/error-handling) + app.UseExceptionHandler("/Error"); + + app.UseHsts(); + } + } + } +} diff --git a/src/aas-api-webapp-registry/appsettings.Development.json b/src/aas-api-webapp-registry/appsettings.Development.json new file mode 100644 index 0000000..8983e0f --- /dev/null +++ b/src/aas-api-webapp-registry/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/src/aas-api-webapp-registry/appsettings.json b/src/aas-api-webapp-registry/appsettings.json new file mode 100644 index 0000000..4e02b91 --- /dev/null +++ b/src/aas-api-webapp-registry/appsettings.json @@ -0,0 +1,18 @@ +{ + "AzureAd": { + "Instance": "https://login.microsoftonline.com/", + "Domain": "microsoft.onmicrosoft.com", + "ClientId": "b285565d-2793-4b48-9deb-73ac063a8ad6", + "TenantId": "72f988bf-86f1-41af-91ab-2d7cd011db47", + "AllowWebApiToBeAuthorizedByACL": true + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*", + "OPENAPI_JSON_VERSION_2": false +} diff --git a/src/aas-api-webapp-registry/web.config b/src/aas-api-webapp-registry/web.config new file mode 100644 index 0000000..6250eaf --- /dev/null +++ b/src/aas-api-webapp-registry/web.config @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/aas-api-webapp-registry/wwwroot/README.md b/src/aas-api-webapp-registry/wwwroot/README.md new file mode 100644 index 0000000..6a0b784 --- /dev/null +++ b/src/aas-api-webapp-registry/wwwroot/README.md @@ -0,0 +1,42 @@ +# Welcome to ASP.NET 5 Preview + +We've made some big updates in this release, so it’s **important** that you spend a few minutes to learn what’s new. + +ASP.NET 5 has been rearchitected to make it **lean** and **composable**. It's fully **open source** and available on [GitHub](http://go.microsoft.com/fwlink/?LinkID=517854). +Your new project automatically takes advantage of modern client-side utilities like [Bower](http://go.microsoft.com/fwlink/?LinkId=518004) and [npm](http://go.microsoft.com/fwlink/?LinkId=518005) (to add client-side libraries) and [Gulp](http://go.microsoft.com/fwlink/?LinkId=518007) (for client-side build and automation tasks). + +We hope you enjoy the new capabilities in ASP.NET 5 and Visual Studio 2015. +The ASP.NET Team + +### You've created a new ASP.NET 5 project. [Learn what's new](http://go.microsoft.com/fwlink/?LinkId=518016) + +### This application consists of: +* Sample pages using ASP.NET MVC 6 +* [Gulp](http://go.microsoft.com/fwlink/?LinkId=518007) and [Bower](http://go.microsoft.com/fwlink/?LinkId=518004) for managing client-side resources +* Theming using [Bootstrap](http://go.microsoft.com/fwlink/?LinkID=398939) + +#### NEW CONCEPTS +* [The 'wwwroot' explained](http://go.microsoft.com/fwlink/?LinkId=518008) +* [Configuration in ASP.NET 5](http://go.microsoft.com/fwlink/?LinkId=518012) +* [Dependency Injection](http://go.microsoft.com/fwlink/?LinkId=518013) +* [Razor TagHelpers](http://go.microsoft.com/fwlink/?LinkId=518014) +* [Manage client packages using Gulp](http://go.microsoft.com/fwlink/?LinkID=517849) +* [Develop on different platforms](http://go.microsoft.com/fwlink/?LinkID=517850) + +#### CUSTOMIZE APP +* [Add Controllers and Views](http://go.microsoft.com/fwlink/?LinkID=398600) +* [Add Data using EntityFramework](http://go.microsoft.com/fwlink/?LinkID=398602) +* [Add Authentication using Identity](http://go.microsoft.com/fwlink/?LinkID=398603) +* [Add real time support using SignalR](http://go.microsoft.com/fwlink/?LinkID=398606) +* [Add Class library](http://go.microsoft.com/fwlink/?LinkID=398604) +* [Add Web APIs with MVC 6](http://go.microsoft.com/fwlink/?LinkId=518009) +* [Add client packages using Bower](http://go.microsoft.com/fwlink/?LinkID=517848) + +#### DEPLOY +* [Run your app locally](http://go.microsoft.com/fwlink/?LinkID=517851) +* [Run your app on ASP.NET Core 5](http://go.microsoft.com/fwlink/?LinkID=517852) +* [Run commands in your 'project.json'](http://go.microsoft.com/fwlink/?LinkID=517853) +* [Publish to Microsoft Azure Web Sites](http://go.microsoft.com/fwlink/?LinkID=398609) +* [Publish to the file system](http://go.microsoft.com/fwlink/?LinkId=518019) + +We would love to hear your [feedback](http://go.microsoft.com/fwlink/?LinkId=518015) diff --git a/src/aas-api-webapp-registry/wwwroot/index.html b/src/aas-api-webapp-registry/wwwroot/index.html new file mode 100644 index 0000000..cde1f2f --- /dev/null +++ b/src/aas-api-webapp-registry/wwwroot/index.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/aas-api-webapp-registry/wwwroot/swagger-original-file.json b/src/aas-api-webapp-registry/wwwroot/swagger-original-file.json new file mode 100644 index 0000000..5862213 --- /dev/null +++ b/src/aas-api-webapp-registry/wwwroot/swagger-original-file.json @@ -0,0 +1,252 @@ +{ + "openapi" : "3.0.3", + "info" : { + "title" : "Asset Administration Shell Part 2 | HTTP/REST | File Interface", + "description" : "The File Server Interface as part of Details of the Asset Administration Shell Part 2", + "termsOfService" : "https://github.com/admin-shell-io/aas-specs", + "contact" : { + "name" : "Michael Hoffmeister, Torben Miny, Andreas Orzelski, Manuel Sauer, Constantin Ziesche" + }, + "version" : "Final-Draft" + }, + "servers" : [ { + "url" : "/" + } ], + "paths" : { + "/packages" : { + "get" : { + "tags" : [ "AASX File Server Interface" ], + "summary" : "Returns a list of available AASX packages at the server", + "operationId" : "GetAllAASXPackageIds", + "parameters" : [ { + "name" : "aasId", + "in" : "query", + "description" : "The Asset Administration Shell’s unique id (BASE64-URL-encoded)", + "required" : false, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Requested package list", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PackageDescription" + }, + "x-content-type" : "application/json" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetAllAASXPackageIds/1/0/RC02" ] + }, + "post" : { + "tags" : [ "AASX File Server Interface" ], + "summary" : "Stores the AASX package at the server", + "operationId" : "PostAASXPackage", + "requestBody" : { + "description" : "AASX package", + "content" : { + "multipart/form-data" : { + "schema" : { + "$ref" : "#/components/schemas/packages_body" + }, + "encoding" : { + "file" : { + "contentType" : "application/asset-administration-shell-package", + "style" : "form" + } + } + } + }, + "required" : true + }, + "responses" : { + "201" : { + "description" : "AASX package stored successfully", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/PackageDescription" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PostAASXPackage/1/0/RC02" ] + } + }, + "/packages/{packageId}" : { + "get" : { + "tags" : [ "AASX File Server Interface" ], + "summary" : "Returns a specific AASX package from the server", + "operationId" : "GetAASXByPackageId", + "parameters" : [ { + "name" : "packageId", + "in" : "path", + "description" : "The package Id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Requested AASX package", + "headers" : { + "X-FileName" : { + "description" : "Filename of the package", + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } + }, + "content" : { + "application/asset-administration-shell-package" : { + "schema" : { + "type" : "string", + "format" : "binary", + "x-content-type" : "application/asset-administration-shell-package" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetAASXByPackageId/1/0/RC02" ] + }, + "put" : { + "tags" : [ "AASX File Server Interface" ], + "summary" : "Updates the AASX package at the server", + "operationId" : "PutAASXByPackageId", + "parameters" : [ { + "name" : "packageId", + "in" : "path", + "description" : "The Package Id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "AASX package", + "content" : { + "multipart/form-data" : { + "schema" : { + "$ref" : "#/components/schemas/packages_packageId_body" + }, + "encoding" : { + "file" : { + "contentType" : "application/asset-administration-shell-package", + "style" : "form" + } + } + } + }, + "required" : true + }, + "responses" : { + "204" : { + "description" : "AASX package updated successfully" + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PutAASXByPackageId/1/0/RC02" ] + }, + "delete" : { + "tags" : [ "AASX File Server Interface" ], + "summary" : "Deletes a specific AASX package from the server", + "operationId" : "DeleteAASXByPackageId", + "parameters" : [ { + "name" : "packageId", + "in" : "path", + "description" : "The Package Id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "204" : { + "description" : "Deleted successfully" + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/DeleteAASXByPackageId/1/0/RC02" ] + } + } + }, + "components" : { + "schemas" : { + "Identifier" : { + "type" : "string" + }, + "PackageDescription" : { + "type" : "object", + "properties" : { + "aasIds" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Identifier" + } + }, + "packageId" : { + "type" : "string" + } + }, + "example" : { + "aasIds" : [ "aasIds", "aasIds" ], + "packageId" : "packageId" + } + }, + "packages_body" : { + "type" : "object", + "properties" : { + "aasIds" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Identifier" + } + }, + "file" : { + "type" : "string", + "format" : "binary" + }, + "fileName" : { + "type" : "string" + } + } + }, + "packages_packageId_body" : { + "type" : "object", + "properties" : { + "aasIds" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Identifier" + } + }, + "file" : { + "type" : "string", + "format" : "binary" + }, + "fileName" : { + "type" : "string" + } + } + } + } + } +} diff --git a/src/aas-api-webapp-registry/wwwroot/swagger-original.json b/src/aas-api-webapp-registry/wwwroot/swagger-original.json new file mode 100644 index 0000000..feb947a --- /dev/null +++ b/src/aas-api-webapp-registry/wwwroot/swagger-original.json @@ -0,0 +1,3576 @@ +{ + "openapi" : "3.0.3", + "info" : { + "title" : "DotAAS Part 2 | HTTP/REST | Entire Interface Collection", + "description" : "The entire interface collection as part of Details of the Asset Administration Shell Part 2", + "termsOfService" : "https://github.com/admin-shell-io/aas-specs", + "contact" : { + "name" : "Michael Hoffmeister, Torben Miny, Andreas Orzelski, Manuel Sauer, Constantin Ziesche" + }, + "version" : "Final-Draft" + }, + "servers" : [ { + "url" : "/" + } ], + "paths" : { + "/shells" : { + "get" : { + "tags" : [ "Asset Administration Shell Repository Interface" ], + "summary" : "Returns all Asset Administration Shells", + "operationId" : "GetAllAssetAdministrationShells", + "parameters" : [ { + "name" : "assetIds", + "in" : "query", + "description" : "The key-value-pair of an Asset identifier", + "required" : false, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "array", + "example" : "[{\"key\": \"globalAssetId\",\"value\": \"http://example.company/myAsset\"},{\"key\": \"myOwnInternalAssetId\",\"value\": \"12345ABC\"}]", + "items" : { + "$ref" : "#/components/schemas/IdentifierKeyValuePair" + } + } + }, { + "name" : "idShort", + "in" : "query", + "description" : "The Asset Administration Shell’s IdShort", + "required" : false, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Requested Asset Administration Shells", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/AssetAdministrationShell" + }, + "x-content-type" : "application/json" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetAllAssetAdministrationShells/1/0/RC02", "https://admin-shell.io/aas/API/GetAllAssetAdministrationShellsByAssetId/1/0/RC02", "https://admin-shell.io/aas/API/GetAllAssetAdministrationShellsByIdShort/1/0/RC02" ] + }, + "post" : { + "tags" : [ "Asset Administration Shell Repository Interface" ], + "summary" : "Creates a new Asset Administration Shell", + "operationId" : "PostAssetAdministrationShell", + "requestBody" : { + "description" : "Asset Administration Shell object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AssetAdministrationShell" + } + } + }, + "required" : true + }, + "responses" : { + "201" : { + "description" : "Asset Administration Shell created successfully", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AssetAdministrationShell" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PostAssetAdministrationShell/1/0/RC02" ] + } + }, + "/shells/{aasIdentifier}" : { + "get" : { + "tags" : [ "Asset Administration Shell Repository Interface" ], + "summary" : "Returns a specific Asset Administration Shell", + "operationId" : "GetAssetAdministrationShellById", + "parameters" : [ { + "name" : "aasIdentifier", + "in" : "path", + "description" : "The Asset Administration Shell’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Requested Asset Administration Shell", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AssetAdministrationShell" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetAssetAdministrationShellById/1/0/RC02" ] + }, + "put" : { + "tags" : [ "Asset Administration Shell Repository Interface" ], + "summary" : "Updates an existing Asset Administration Shell", + "operationId" : "PutAssetAdministrationShellById", + "parameters" : [ { + "name" : "aasIdentifier", + "in" : "path", + "description" : "The Asset Administration Shell’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "Asset Administration Shell object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AssetAdministrationShell" + } + } + }, + "required" : true + }, + "responses" : { + "204" : { + "description" : "Asset Administration Shell updated successfully" + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PutAssetAdministrationShellById/1/0/RC02" ] + }, + "delete" : { + "tags" : [ "Asset Administration Shell Repository Interface" ], + "summary" : "Deletes an Asset Administration Shell", + "operationId" : "DeleteAssetAdministrationShellById", + "parameters" : [ { + "name" : "aasIdentifier", + "in" : "path", + "description" : "The Asset Administration Shell’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "204" : { + "description" : "Asset Administration Shell deleted successfully" + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/DeleteAssetAdministrationShellById/1/0/RC02" ] + } + }, + "/aas" : { + "get" : { + "tags" : [ "Asset Administration Shell Interface" ], + "summary" : "Returns the Asset Administration Shell", + "operationId" : "GetAssetAdministrationShell", + "parameters" : [ { + "name" : "content", + "in" : "query", + "description" : "Determines the request or response kind of the resource", + "required" : false, + "schema" : { + "type" : "string", + "default" : "normal", + "enum" : [ "normal", "trimmed", "value", "reference", "path" ] + } + } ], + "responses" : { + "200" : { + "description" : "Requested Asset Administration Shell", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AssetAdministrationShell" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetAssetAdministrationShell/1/0/RC02" ] + }, + "put" : { + "tags" : [ "Asset Administration Shell Interface" ], + "summary" : "Updates the Asset Administration Shell", + "operationId" : "PutAssetAdministrationShell", + "parameters" : [ { + "name" : "content", + "in" : "query", + "description" : "Determines the request or response kind of the resource", + "required" : false, + "schema" : { + "type" : "string", + "default" : "normal", + "enum" : [ "normal", "trimmed", "value", "reference", "path" ] + } + } ], + "requestBody" : { + "description" : "Asset Administration Shell object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AssetAdministrationShell" + } + } + }, + "required" : true + }, + "responses" : { + "204" : { + "description" : "Asset Administration Shell updated successfully" + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PutAssetAdministrationShell/1/0/RC02" ] + } + }, + "/aas/asset-information" : { + "get" : { + "tags" : [ "Asset Administration Shell Interface" ], + "summary" : "Returns the Asset Information", + "operationId" : "GetAssetInformation", + "responses" : { + "200" : { + "description" : "Requested Asset Information", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AssetInformation" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetAssetInformation/1/0/RC02" ] + }, + "put" : { + "tags" : [ "Asset Administration Shell Interface" ], + "summary" : "Updates the Asset Information", + "operationId" : "PutAssetInformation", + "requestBody" : { + "description" : "Asset Information object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AssetInformation" + } + } + }, + "required" : true + }, + "responses" : { + "204" : { + "description" : "Asset Information updated successfully" + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PutAssetInformation/1/0/RC02" ] + } + }, + "/aas/submodels" : { + "get" : { + "tags" : [ "Asset Administration Shell Interface" ], + "summary" : "Returns all submodel references", + "operationId" : "GetAllSubmodelReferences", + "responses" : { + "200" : { + "description" : "Requested submodel references", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Reference" + }, + "x-content-type" : "application/json" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetAllSubmodelReferences/1/0/RC02" ] + }, + "post" : { + "tags" : [ "Asset Administration Shell Interface" ], + "summary" : "Creates a submodel reference at the Asset Administration Shell", + "operationId" : "PostSubmodelReference", + "requestBody" : { + "description" : "Reference to the Submodel", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Reference" + } + } + }, + "required" : true + }, + "responses" : { + "201" : { + "description" : "Submodel reference created successfully", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Reference" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PostSubmodelReference/1/0/RC02" ] + } + }, + "/aas/submodels/{submodelIdentifier}" : { + "delete" : { + "tags" : [ "Asset Administration Shell Interface" ], + "summary" : "Deletes the submodel reference from the Asset Administration Shell", + "operationId" : "DeleteSubmodelReferenceById", + "parameters" : [ { + "name" : "submodelIdentifier", + "in" : "path", + "description" : "The Submodel’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "204" : { + "description" : "Submodel reference deleted successfully" + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/DeleteSubmodelReference/1/0/RC02" ] + } + }, + "/submodels" : { + "get" : { + "tags" : [ "Submodel Repository Interface" ], + "summary" : "Returns all Submodels", + "operationId" : "GetAllSubmodels", + "parameters" : [ { + "name" : "semanticId", + "in" : "query", + "description" : "The value of the semantic id reference (BASE64-URL-encoded)", + "schema" : { + "type" : "string" + } + }, { + "name" : "idShort", + "in" : "query", + "description" : "The Submodel’s idShort", + "required" : false, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Requested Submodels", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Submodel" + }, + "x-content-type" : "application/json" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetAllSubmodels/1/0/RC02", "https://admin-shell.io/aas/API/GetAllSubmodelsBySemanticId/1/0/RC02", "https://admin-shell.io/aas/API/GetAllSubmodelsByIdShort/1/0/RC02" ] + }, + "post" : { + "tags" : [ "Submodel Repository Interface" ], + "summary" : "Creates a new Submodel", + "operationId" : "PostSubmodel", + "requestBody" : { + "description" : "Submodel object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Submodel" + } + } + }, + "required" : true + }, + "responses" : { + "201" : { + "description" : "Submodel created successfully", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Submodel" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PostSubmodel/1/0/RC02" ] + } + }, + "/submodels/{submodelIdentifier}" : { + "get" : { + "tags" : [ "Submodel Repository Interface" ], + "summary" : "Returns a specific Submodel", + "operationId" : "GetSubmodelById", + "parameters" : [ { + "name" : "submodelIdentifier", + "in" : "path", + "description" : "The Submodel’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Requested Submodel", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Submodel" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetSubmodelById/1/0/RC02" ] + }, + "put" : { + "tags" : [ "Submodel Repository Interface" ], + "summary" : "Creates a new or updates an existing Submodel", + "operationId" : "PutSubmodelById", + "parameters" : [ { + "name" : "submodelIdentifier", + "in" : "path", + "description" : "The Submodel’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "Submodel object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Submodel" + } + } + }, + "required" : true + }, + "responses" : { + "201" : { + "description" : "Submodel created successfully", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Submodel" + } + } + } + }, + "204" : { + "description" : "Submodel updated successfully" + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PutSubmodelById/1/0/RC02" ] + }, + "delete" : { + "tags" : [ "Submodel Repository Interface" ], + "summary" : "Deletes a Submodel", + "operationId" : "DeleteSubmodelById", + "parameters" : [ { + "name" : "submodelIdentifier", + "in" : "path", + "description" : "The Submodel’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "204" : { + "description" : "Submodel deleted successfully" + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/DeleteSubmodelById/1/0/RC02" ] + } + }, + "/submodel" : { + "get" : { + "tags" : [ "Submodel Interface" ], + "summary" : "Returns the Submodel", + "operationId" : "GetSubmodel", + "parameters" : [ { + "name" : "level", + "in" : "query", + "description" : "Determines the structural depth of the respective resource content", + "required" : false, + "schema" : { + "type" : "string", + "default" : "deep", + "enum" : [ "deep", "core" ] + } + }, { + "name" : "content", + "in" : "query", + "description" : "Determines the request or response kind of the resource", + "required" : false, + "schema" : { + "type" : "string", + "default" : "normal", + "enum" : [ "normal", "trimmed", "value", "reference", "path" ] + } + }, { + "name" : "extent", + "in" : "query", + "description" : "Determines to which extent the resource is being serialized", + "required" : false, + "schema" : { + "type" : "string", + "enum" : [ "withBlobValue", "withoutBlobValue" ] + } + } ], + "responses" : { + "200" : { + "description" : "Requested Submodel", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Submodel" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetSubmodel/1/0/RC02" ] + }, + "put" : { + "tags" : [ "Submodel Interface" ], + "summary" : "Updates the Submodel", + "operationId" : "PutSubmodel", + "parameters" : [ { + "name" : "level", + "in" : "query", + "description" : "Determines the structural depth of the respective resource content", + "required" : false, + "schema" : { + "type" : "string", + "default" : "deep", + "enum" : [ "deep", "core" ] + } + }, { + "name" : "content", + "in" : "query", + "description" : "Determines the request or response kind of the resource", + "required" : false, + "schema" : { + "type" : "string", + "default" : "normal", + "enum" : [ "normal", "trimmed", "value", "reference", "path" ] + } + }, { + "name" : "extent", + "in" : "query", + "description" : "Determines to which extent the resource is being serialized", + "required" : false, + "schema" : { + "type" : "string", + "enum" : [ "withBlobValue", "withoutBlobValue" ] + } + } ], + "requestBody" : { + "description" : "Submodel object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Submodel" + } + } + }, + "required" : true + }, + "responses" : { + "204" : { + "description" : "Submodel updated successfully" + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PutSubmodel/1/0/RC02" ] + } + }, + "/submodel/submodel-elements" : { + "get" : { + "tags" : [ "Submodel Interface" ], + "summary" : "Returns all submodel elements including their hierarchy", + "operationId" : "GetAllSubmodelElements", + "parameters" : [ { + "name" : "level", + "in" : "query", + "description" : "Determines the structural depth of the respective resource content", + "required" : false, + "schema" : { + "type" : "string", + "default" : "deep", + "enum" : [ "deep", "core" ] + } + }, { + "name" : "content", + "in" : "query", + "description" : "Determines the request or response kind of the resource", + "required" : false, + "schema" : { + "type" : "string", + "default" : "normal", + "enum" : [ "normal", "trimmed", "value", "reference", "path" ] + } + }, { + "name" : "extent", + "in" : "query", + "description" : "Determines to which extent the resource is being serialized", + "required" : false, + "schema" : { + "type" : "string", + "enum" : [ "withBlobValue", "withoutBlobValue" ] + } + } ], + "responses" : { + "200" : { + "description" : "List of found submodel elements", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/SubmodelElement" + }, + "x-content-type" : "application/json" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetAllSubmodelElements/1/0/RC02", "https://admin-shell.io/aas/API/GetAllSubmodelElementsBySemanticId/1/0/RC02", "https://admin-shell.io/aas/API/GetAllSubmodelElementsByParentPathAndSemanticId/1/0/RC02" ] + }, + "post" : { + "tags" : [ "Submodel Interface" ], + "summary" : "Creates a new submodel element", + "operationId" : "PostSubmodelElement", + "parameters" : [ { + "name" : "level", + "in" : "query", + "description" : "Determines the structural depth of the respective resource content", + "required" : false, + "schema" : { + "type" : "string", + "default" : "deep", + "enum" : [ "deep", "core" ] + } + }, { + "name" : "content", + "in" : "query", + "description" : "Determines the request or response kind of the resource", + "required" : false, + "schema" : { + "type" : "string", + "default" : "normal", + "enum" : [ "normal", "trimmed", "value", "reference", "path" ] + } + }, { + "name" : "extent", + "in" : "query", + "description" : "Determines to which extent the resource is being serialized", + "required" : false, + "schema" : { + "type" : "string", + "enum" : [ "withBlobValue", "withoutBlobValue" ] + } + } ], + "requestBody" : { + "description" : "Requested submodel element", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SubmodelElement" + } + } + }, + "required" : true + }, + "responses" : { + "201" : { + "description" : "Submodel element created successfully", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SubmodelElement" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PostSubmodelElement/1/0/RC02c" ] + } + }, + "/submodel/submodel-elements/{idShortPath}" : { + "get" : { + "tags" : [ "Submodel Interface" ], + "summary" : "Returns a specific submodel element from the Submodel at a specified path", + "operationId" : "GetSubmodelElementByPath", + "parameters" : [ { + "name" : "level", + "in" : "query", + "description" : "Determines the structural depth of the respective resource content", + "required" : false, + "schema" : { + "type" : "string", + "default" : "deep", + "enum" : [ "deep", "core" ] + } + }, { + "name" : "content", + "in" : "query", + "description" : "Determines the request or response kind of the resource", + "required" : false, + "schema" : { + "type" : "string", + "default" : "normal", + "enum" : [ "normal", "trimmed", "value", "reference", "path" ] + } + }, { + "name" : "extent", + "in" : "query", + "description" : "Determines to which extent the resource is being serialized", + "required" : false, + "schema" : { + "type" : "string", + "enum" : [ "withBlobValue", "withoutBlobValue" ] + } + }, { + "name" : "idShortPath", + "in" : "path", + "description" : "IdShort path to the submodel element (dot-separated)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Requested submodel element", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SubmodelElement" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetSubmodelElementByPath/1/0/RC02" ] + }, + "put" : { + "tags" : [ "Submodel Interface" ], + "summary" : "Updates an existing submodel element at a specified path within submodel elements hierarchy", + "operationId" : "PutSubmodelElementByPath", + "parameters" : [ { + "name" : "level", + "in" : "query", + "description" : "Determines the structural depth of the respective resource content", + "required" : false, + "schema" : { + "type" : "string", + "default" : "deep", + "enum" : [ "deep", "core" ] + } + }, { + "name" : "content", + "in" : "query", + "description" : "Determines the request or response kind of the resource", + "required" : false, + "schema" : { + "type" : "string", + "default" : "normal", + "enum" : [ "normal", "trimmed", "value", "reference", "path" ] + } + }, { + "name" : "extent", + "in" : "query", + "description" : "Determines to which extent the resource is being serialized", + "required" : false, + "schema" : { + "type" : "string", + "enum" : [ "withBlobValue", "withoutBlobValue" ] + } + }, { + "name" : "idShortPath", + "in" : "path", + "description" : "IdShort path to the submodel element (dot-separated)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "Requested submodel element", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SubmodelElement" + } + } + }, + "required" : true + }, + "responses" : { + "204" : { + "description" : "Submodel element updated successfully" + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PutSubmodelElementByPath/1/0/RC02c", "https://admin-shell.io/aas/API/SetSubmodelElementValueByPath/1/0/RC02" ] + }, + "post" : { + "tags" : [ "Submodel Interface" ], + "summary" : "Creates a new submodel element at a specified path within submodel elements hierarchy", + "operationId" : "PostSubmodelElementByPath", + "parameters" : [ { + "name" : "level", + "in" : "query", + "description" : "Determines the structural depth of the respective resource content", + "required" : false, + "schema" : { + "type" : "string", + "default" : "deep", + "enum" : [ "deep", "core" ] + } + }, { + "name" : "content", + "in" : "query", + "description" : "Determines the request or response kind of the resource", + "required" : false, + "schema" : { + "type" : "string", + "default" : "normal", + "enum" : [ "normal", "trimmed", "value", "reference", "path" ] + } + }, { + "name" : "extent", + "in" : "query", + "description" : "Determines to which extent the resource is being serialized", + "required" : false, + "schema" : { + "type" : "string", + "enum" : [ "withBlobValue", "withoutBlobValue" ] + } + }, { + "name" : "idShortPath", + "in" : "path", + "description" : "IdShort path to the submodel element (dot-separated)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "Requested submodel element", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SubmodelElement" + } + } + }, + "required" : true + }, + "responses" : { + "201" : { + "description" : "Submodel element created successfully", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SubmodelElement" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PostSubmodelElementByPath/1/0/RC02c" ] + }, + "delete" : { + "tags" : [ "Submodel Interface" ], + "summary" : "Deletes a submodel element at a specified path within the submodel elements hierarchy", + "operationId" : "DeleteSubmodelElementByPath", + "parameters" : [ { + "name" : "idShortPath", + "in" : "path", + "description" : "IdShort path to the submodel element (dot-separated)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "204" : { + "description" : "Submodel element deleted successfully" + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/DeleteSubmodelElementByPath/1/0/RC02" ] + } + }, + "/submodel/submodel-elements/{idShortPath}/invoke" : { + "post" : { + "tags" : [ "Submodel Interface" ], + "summary" : "Synchronously or asynchronously invokes an Operation at a specified path", + "operationId" : "InvokeOperation", + "parameters" : [ { + "name" : "async", + "in" : "query", + "description" : "Determines whether an operation invocation is performed asynchronously or synchronously", + "required" : false, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "boolean", + "default" : false + } + }, { + "name" : "content", + "in" : "query", + "description" : "Determines the request or response kind of the resource", + "required" : false, + "schema" : { + "type" : "string", + "default" : "normal", + "enum" : [ "normal", "trimmed", "value", "reference", "path" ] + } + }, { + "name" : "idShortPath", + "in" : "path", + "description" : "IdShort path to the submodel element (dot-separated), in this case an operation", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "Operation request object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/OperationRequest" + } + } + }, + "required" : true + }, + "responses" : { + "200" : { + "description" : "Operation result object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/OperationResult" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/InvokeOperationSync/1/0/RC02", "https://admin-shell.io/aas/API/InvokeOperationAsync/1/0/RC02" ] + } + }, + "/submodel/submodel-elements/{idShortPath}/operation-results/{handleId}" : { + "get" : { + "tags" : [ "Submodel Interface" ], + "summary" : "Returns the Operation result of an asynchronous invoked Operation", + "operationId" : "GetOperationAsyncResult", + "parameters" : [ { + "name" : "content", + "in" : "query", + "required" : false, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "string", + "default" : "normal", + "enum" : [ "normal", "value" ] + } + }, { + "name" : "idShortPath", + "in" : "path", + "description" : "IdShort path to the submodel element (dot-separated), in this case an operation", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + }, { + "name" : "handleId", + "in" : "path", + "description" : "The returned handle id of an operation’s asynchronous invocation used to request the current state of the operation’s execution (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Operation result object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/OperationResult" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetOperationAsyncResult/1/0/RC02" ] + } + }, + "/registry/shell-descriptors" : { + "get" : { + "tags" : [ "Asset Administration Shell Registry Interface" ], + "summary" : "Returns all Asset Administration Shell Descriptors", + "operationId" : "GetAllAssetAdministrationShellDescriptors", + "responses" : { + "200" : { + "description" : "Requested Asset Administration Shell Descriptors", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/AssetAdministrationShellDescriptor" + }, + "x-content-type" : "application/json" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetAllAssetAdministrationShellDescriptors/1/0/RC02" ] + }, + "post" : { + "tags" : [ "Asset Administration Shell Registry Interface" ], + "summary" : "Creates a new Asset Administration Shell Descriptor, i.e. registers an AAS", + "operationId" : "PostAssetAdministrationShellDescriptor", + "requestBody" : { + "description" : "Asset Administration Shell Descriptor object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AssetAdministrationShellDescriptor" + } + } + }, + "required" : true + }, + "responses" : { + "201" : { + "description" : "Asset Administration Shell Descriptor created successfully", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AssetAdministrationShellDescriptor" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PostAssetAdministrationShellDescriptor/1/0/RC02" ] + } + }, + "/registry/shell-descriptors/{aasIdentifier}" : { + "get" : { + "tags" : [ "Asset Administration Shell Registry Interface" ], + "summary" : "Returns a specific Asset Administration Shell Descriptor", + "operationId" : "GetAssetAdministrationShellDescriptorById", + "parameters" : [ { + "name" : "aasIdentifier", + "in" : "path", + "description" : "The Asset Administration Shell’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Requested Asset Administration Shell Descriptor", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AssetAdministrationShellDescriptor" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetAssetAdministrationShellDescriptorById/1/0/RC02" ] + }, + "put" : { + "tags" : [ "Asset Administration Shell Registry Interface" ], + "summary" : "Updates an existing Asset Administration Shell Descriptor", + "operationId" : "PutAssetAdministrationShellDescriptorById", + "parameters" : [ { + "name" : "aasIdentifier", + "in" : "path", + "description" : "The Asset Administration Shell’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "Asset Administration Shell Descriptor object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AssetAdministrationShellDescriptor" + } + } + }, + "required" : true + }, + "responses" : { + "204" : { + "description" : "Asset Administration Shell Descriptor updated successfully" + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PutAssetAdministrationShellDescriptorById/1/0/RC02" ] + }, + "delete" : { + "tags" : [ "Asset Administration Shell Registry Interface" ], + "summary" : "Deletes an Asset Administration Shell Descriptor, i.e. de-registers an AAS", + "operationId" : "DeleteAssetAdministrationShellDescriptorById", + "parameters" : [ { + "name" : "aasIdentifier", + "in" : "path", + "description" : "The Asset Administration Shell’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "204" : { + "description" : "Asset Administration Shell Descriptor deleted successfully" + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/DeleteAssetAdministrationShellDescriptorById/1/0/RC02" ] + } + }, + "/registry/shell-descriptors/{aasIdentifier}/submodel-descriptors" : { + "get" : { + "tags" : [ "Asset Administration Shell Registry Interface" ], + "summary" : "Returns all Submodel Descriptors", + "operationId" : "GetAllSubmodelDescriptors_AASRegistry", + "parameters" : [ { + "name" : "aasIdentifier", + "in" : "path", + "description" : "The Asset Administration Shell’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Requested Submodel Descriptors", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/SubmodelDescriptor" + }, + "x-content-type" : "application/json" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetAllSubmodelDescriptors/1/0/RC02" ] + }, + "post" : { + "tags" : [ "Asset Administration Shell Registry Interface" ], + "summary" : "Creates a new Submodel Descriptor, i.e. registers a submodel", + "operationId" : "PostSubmodelDescriptor_AASRegistry", + "parameters" : [ { + "name" : "aasIdentifier", + "in" : "path", + "description" : "The Asset Administration Shell’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "Submodel Descriptor object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SubmodelDescriptor" + } + } + }, + "required" : true + }, + "responses" : { + "201" : { + "description" : "Submodel Descriptor created successfully", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SubmodelDescriptor" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PostSubmodelDescriptor/1/0/RC02" ] + } + }, + "/registry/shell-descriptors/{aasIdentifier}/submodel-descriptors/{submodelIdentifier}" : { + "get" : { + "tags" : [ "Asset Administration Shell Registry Interface" ], + "summary" : "Returns a specific Submodel Descriptor", + "operationId" : "GetSubmodelDescriptorById_AASRegistry", + "parameters" : [ { + "name" : "aasIdentifier", + "in" : "path", + "description" : "The Asset Administration Shell’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + }, { + "name" : "submodelIdentifier", + "in" : "path", + "description" : "The Submodel’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Requested Submodel Descriptor", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SubmodelDescriptor" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetSubmodelDescriptorById/1/0/RC02" ] + }, + "put" : { + "tags" : [ "Asset Administration Shell Registry Interface" ], + "summary" : "Updates an existing Submodel Descriptor", + "operationId" : "PutSubmodelDescriptorById_AASRegistry", + "parameters" : [ { + "name" : "aasIdentifier", + "in" : "path", + "description" : "The Asset Administration Shell’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + }, { + "name" : "submodelIdentifier", + "in" : "path", + "description" : "The Submodel’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "Submodel Descriptor object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SubmodelDescriptor" + } + } + }, + "required" : true + }, + "responses" : { + "204" : { + "description" : "Submodel Descriptor updated successfully" + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PutSubmodelDescriptorById/1/0/RC02" ] + }, + "delete" : { + "tags" : [ "Asset Administration Shell Registry Interface" ], + "summary" : "Deletes a Submodel Descriptor, i.e. de-registers a submodel", + "operationId" : "DeleteSubmodelDescriptorById_AASRegistry", + "parameters" : [ { + "name" : "aasIdentifier", + "in" : "path", + "description" : "The Asset Administration Shell’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + }, { + "name" : "submodelIdentifier", + "in" : "path", + "description" : "The Submodel’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "204" : { + "description" : "Submodel Descriptor deleted successfully" + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/DeleteSubmodelDescriptorById/1/0/RC02" ] + } + }, + "/registry/submodel-descriptors" : { + "get" : { + "tags" : [ "Submodel Registry Interface" ], + "summary" : "Returns all Submodel Descriptors", + "operationId" : "GetAllSubmodelDescriptors", + "responses" : { + "200" : { + "description" : "Requested Submodel Descriptors", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/SubmodelDescriptor" + }, + "x-content-type" : "application/json" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetAllSubmodelDescriptors/1/0/RC02" ] + }, + "post" : { + "tags" : [ "Submodel Registry Interface" ], + "summary" : "Creates a new Submodel Descriptor, i.e. registers a submodel", + "operationId" : "PostSubmodelDescriptor", + "requestBody" : { + "description" : "Submodel Descriptor object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SubmodelDescriptor" + } + } + }, + "required" : true + }, + "responses" : { + "201" : { + "description" : "Submodel Descriptor created successfully", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SubmodelDescriptor" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PostSubmodelDescriptor/1/0/RC02" ] + } + }, + "/registry/submodel-descriptors/{submodelIdentifier}" : { + "get" : { + "tags" : [ "Submodel Registry Interface" ], + "summary" : "Returns a specific Submodel Descriptor", + "operationId" : "GetSubmodelDescriptorById", + "parameters" : [ { + "name" : "submodelIdentifier", + "in" : "path", + "description" : "The Submodel’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Requested Submodel Descriptor", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SubmodelDescriptor" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetSubmodelDescriptorById/1/0/RC02" ] + }, + "put" : { + "tags" : [ "Submodel Registry Interface" ], + "summary" : "Updates an existing Submodel Descriptor", + "operationId" : "PutSubmodelDescriptorById", + "parameters" : [ { + "name" : "submodelIdentifier", + "in" : "path", + "description" : "The Submodel’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "Submodel Descriptor object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SubmodelDescriptor" + } + } + }, + "required" : true + }, + "responses" : { + "204" : { + "description" : "Submodel Descriptor updated successfully" + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PutSubmodelDescriptorById/1/0/RC02" ] + }, + "delete" : { + "tags" : [ "Submodel Registry Interface" ], + "summary" : "Deletes a Submodel Descriptor, i.e. de-registers a submodel", + "operationId" : "DeleteSubmodelDescriptorById", + "parameters" : [ { + "name" : "submodelIdentifier", + "in" : "path", + "description" : "The Submodel’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "204" : { + "description" : "Submodel Descriptor deleted successfully" + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/DeleteSubmodelDescriptorById/1/0/RC02" ] + } + }, + "/concept-descriptions" : { + "get" : { + "tags" : [ "Concept Description Repository Interface" ], + "summary" : "Returns all Concept Descriptions", + "operationId" : "GetAllConceptDescriptions", + "parameters" : [ { + "name" : "idShort", + "in" : "query", + "description" : "The Concept Description’s IdShort", + "required" : false, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "isCaseOf", + "in" : "query", + "description" : "IsCaseOf reference (BASE64-URL-encoded)", + "required" : false, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "dataSpecificationRef", + "in" : "query", + "description" : "DataSpecification reference (BASE64-URL-encoded)", + "required" : false, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Requested Concept Descriptions", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ConceptDescription" + }, + "x-content-type" : "application/json" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetAllConceptDescriptions/1/0/RC02", "https://admin-shell.io/aas/API/GetAllConceptDescriptionsByIdShort/1/0/RC02", "https://admin-shell.io/aas/API/GetAllConceptDescriptionsByIsCaseOf/1/0/RC02", "https://admin-shell.io/aas/API/GetAllConceptDescriptionsByDataSpecificationReference/1/0/RC02" ] + }, + "post" : { + "tags" : [ "Concept Description Repository Interface" ], + "summary" : "Creates a new Concept Description", + "operationId" : "PostConceptDescription", + "requestBody" : { + "description" : "Concept Description object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ConceptDescription" + } + } + }, + "required" : true + }, + "responses" : { + "201" : { + "description" : "Concept Description created successfully", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ConceptDescription" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PostConceptDescription/1/0/RC02" ] + } + }, + "/concept-descriptions/{cdIdentifier}" : { + "get" : { + "tags" : [ "Concept Description Repository Interface" ], + "summary" : "Returns a specific Concept Description", + "operationId" : "GetConceptDescriptionById", + "parameters" : [ { + "name" : "cdIdentifier", + "in" : "path", + "description" : "The Concept Description’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Requested Concept Description", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ConceptDescription" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetConceptDescriptionById/1/0/RC02" ] + }, + "put" : { + "tags" : [ "Concept Description Repository Interface" ], + "summary" : "Updates an existing Concept Description", + "operationId" : "PutConceptDescriptionById", + "parameters" : [ { + "name" : "cdIdentifier", + "in" : "path", + "description" : "The Concept Description’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "Concept Description object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ConceptDescription" + } + } + }, + "required" : true + }, + "responses" : { + "204" : { + "description" : "Concept Description updated successfully" + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PutConceptDescriptionById/1/0/RC02" ] + }, + "delete" : { + "tags" : [ "Concept Description Repository Interface" ], + "summary" : "Deletes a Concept Description", + "operationId" : "DeleteConceptDescriptionById", + "parameters" : [ { + "name" : "cdIdentifier", + "in" : "path", + "description" : "The Concept Description’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "204" : { + "description" : "Concept Description deleted successfully" + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/DeleteConceptDescriptionById/1/0/RC02" ] + } + }, + "/packages" : { + "get" : { + "tags" : [ "AASX File Server Interface" ], + "summary" : "Returns a list of available AASX packages at the server", + "operationId" : "GetAllAASXPackageIds", + "parameters" : [ { + "name" : "aasId", + "in" : "query", + "description" : "The Asset Administration Shell’s unique id (BASE64-URL-encoded)", + "required" : false, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Requested package list", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PackageDescription" + }, + "x-content-type" : "application/json" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetAllAASXPackageIds/1/0/RC02" ] + }, + "post" : { + "tags" : [ "AASX File Server Interface" ], + "summary" : "Stores the AASX package at the server", + "operationId" : "PostAASXPackage", + "requestBody" : { + "description" : "AASX package", + "content" : { + "multipart/form-data" : { + "schema" : { + "$ref" : "#/components/schemas/packages_body" + }, + "encoding" : { + "file" : { + "contentType" : "application/asset-administration-shell-package", + "style" : "form" + } + } + } + }, + "required" : true + }, + "responses" : { + "201" : { + "description" : "AASX package stored successfully", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/PackageDescription" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PostAASXPackage/1/0/RC02" ] + } + }, + "/packages/{packageId}" : { + "get" : { + "tags" : [ "AASX File Server Interface" ], + "summary" : "Returns a specific AASX package from the server", + "operationId" : "GetAASXByPackageId", + "parameters" : [ { + "name" : "packageId", + "in" : "path", + "description" : "The package Id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Requested AASX package", + "headers" : { + "X-FileName" : { + "description" : "Filename of the package", + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } + }, + "content" : { + "application/asset-administration-shell-package" : { + "schema" : { + "type" : "string", + "format" : "binary", + "x-content-type" : "application/asset-administration-shell-package" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetAASXByPackageId/1/0/RC02" ] + }, + "put" : { + "tags" : [ "AASX File Server Interface" ], + "summary" : "Updates the AASX package at the server", + "operationId" : "PutAASXByPackageId", + "parameters" : [ { + "name" : "packageId", + "in" : "path", + "description" : "The Package Id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "AASX package", + "content" : { + "multipart/form-data" : { + "schema" : { + "$ref" : "#/components/schemas/packages_packageId_body" + }, + "encoding" : { + "file" : { + "contentType" : "application/asset-administration-shell-package", + "style" : "form" + } + } + } + }, + "required" : true + }, + "responses" : { + "204" : { + "description" : "AASX package updated successfully" + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PutAASXByPackageId/1/0/RC02" ] + }, + "delete" : { + "tags" : [ "AASX File Server Interface" ], + "summary" : "Deletes a specific AASX package from the server", + "operationId" : "DeleteAASXByPackageId", + "parameters" : [ { + "name" : "packageId", + "in" : "path", + "description" : "The Package Id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "204" : { + "description" : "Deleted successfully" + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/DeleteAASXByPackageId/1/0/RC02" ] + } + }, + "/serialization" : { + "get" : { + "tags" : [ "Asset Administration Shell Serialization Interface" ], + "summary" : "Returns an appropriate serialization based on the specified format (see SerializationFormat)", + "operationId" : "GenerateSerializationByIds", + "parameters" : [ { + "name" : "aasIds", + "in" : "query", + "description" : "The Asset Administration Shells' unique ids (BASE64-URL-encoded)", + "required" : true, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Identifier" + } + } + }, { + "name" : "submodelIds", + "in" : "query", + "description" : "The Submodels' unique ids (BASE64-URL-encoded)", + "required" : true, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Identifier" + } + } + }, { + "name" : "includeConceptDescriptions", + "in" : "query", + "description" : "Include Concept Descriptions?", + "required" : true, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "boolean", + "default" : true + } + } ], + "responses" : { + "200" : { + "description" : "Requested serialization based on SerializationFormat", + "content" : { + "application/asset-administration-shell-package+xml" : { + "schema" : { + "type" : "string", + "description" : "AASX package", + "format" : "binary", + "x-content-type" : "application/asset-administration-shell-package+xml" + } + }, + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AssetAdministrationShellEnvironment" + } + }, + "application/xml" : { + "schema" : { + "$ref" : "#/components/schemas/AssetAdministrationShellEnvironment" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GenerateSerializationByIds/1/0/RC02" ] + } + }, + "/lookup/shells" : { + "get" : { + "tags" : [ "Asset Administration Shell Basic Discovery" ], + "summary" : "Returns a list of Asset Administration Shell ids based on Asset identifier key-value-pairs", + "operationId" : "GetAllAssetAdministrationShellIdsByAssetLink", + "parameters" : [ { + "name" : "assetIds", + "in" : "query", + "description" : "The key-value-pair of an Asset identifier", + "required" : false, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "array", + "example" : "[{\"key\": \"globalAssetId\",\"value\": \"http://example.company/myAsset\"},{\"key\": \"myOwnInternalAssetId\",\"value\": \"12345ABC\"}]", + "items" : { + "$ref" : "#/components/schemas/IdentifierKeyValuePair" + } + } + } ], + "responses" : { + "200" : { + "description" : "Requested Asset Administration Shell ids", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Identifier" + }, + "x-content-type" : "application/json" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetAllAssetAdministrationShellIdsByAssetLink/1/0/RC02" ] + } + }, + "/lookup/shells/{aasIdentifier}" : { + "get" : { + "tags" : [ "Asset Administration Shell Basic Discovery" ], + "summary" : "Returns a list of Asset identifier key-value-pairs based on an Asset Administration Shell id to edit discoverable content", + "operationId" : "GetAllAssetLinksById", + "parameters" : [ { + "name" : "aasIdentifier", + "in" : "path", + "description" : "The Asset Administration Shell’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Requested Asset identifier key-value-pairs", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/IdentifierKeyValuePair" + }, + "x-content-type" : "application/json" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/GetAllAssetLinksById/1/0/RC02" ] + }, + "post" : { + "tags" : [ "Asset Administration Shell Basic Discovery" ], + "summary" : "Creates all Asset identifier key-value-pair linked to an Asset Administration Shell to edit discoverable content", + "operationId" : "PostAllAssetLinksById", + "parameters" : [ { + "name" : "aasIdentifier", + "in" : "path", + "description" : "The Asset Administration Shell’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "Asset identifier key-value-pairs", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/IdentifierKeyValuePair" + } + } + } + }, + "required" : true + }, + "responses" : { + "201" : { + "description" : "Asset identifier key-value-pairs created successfully", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/IdentifierKeyValuePair" + }, + "x-content-type" : "application/json" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/PostAllAssetLinksById/1/0/RC02" ] + }, + "delete" : { + "tags" : [ "Asset Administration Shell Basic Discovery" ], + "summary" : "Deletes all Asset identifier key-value-pair linked to an Asset Administration Shell to edit discoverable content", + "operationId" : "DeleteAllAssetLinksById", + "parameters" : [ { + "name" : "aasIdentifier", + "in" : "path", + "description" : "The Asset Administration Shell’s unique id (BASE64-URL-encoded)", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "204" : { + "description" : "Asset identifier key-value-pairs deleted successfully" + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/DeleteAllAssetLinksById/1/0/RC02" ] + } + }, + "/descriptor" : { + "get" : { + "tags" : [ "Descriptor Interface" ], + "summary" : "Returns the self-describing information of a network resource (Descriptor)", + "operationId" : "GetDescriptor", + "responses" : { + "200" : { + "description" : "Requested Descriptor", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Descriptor" + }, + "x-content-type" : "application/json" + } + } + } + } + }, + "x-semanticIds" : [ "https://admin-shell.io/aas/API/Descriptor/GetDescriptor/1/0/RC02" ] + } + } + }, + "components" : { + "schemas" : { + "IdentifierKeyValuePair" : { + "allOf" : [ { + "$ref" : "#/components/schemas/HasSemantics" + }, { + "required" : [ "key", "subjectId", "value" ], + "properties" : { + "key" : { + "type" : "string" + }, + "subjectId" : { + "$ref" : "#/components/schemas/Reference" + }, + "value" : { + "type" : "string" + } + } + } ] + }, + "HasSemantics" : { + "type" : "object", + "properties" : { + "semanticId" : { + "$ref" : "#/components/schemas/Reference" + } + } + }, + "Reference" : { + "type" : "object", + "oneOf" : [ { + "$ref" : "#/components/schemas/GlobalReference" + }, { + "$ref" : "#/components/schemas/ModelReference" + } ] + }, + "GlobalReference" : { + "required" : [ "value" ], + "properties" : { + "value" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Identifier" + } + } + }, + "allOf" : [ { + "$ref" : "#/components/schemas/Reference" + } ] + }, + "Identifier" : { + "type" : "string" + }, + "ModelReference" : { + "required" : [ "keys" ], + "properties" : { + "referredSemanticId" : { + "$ref" : "#/components/schemas/Reference" + }, + "keys" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Key" + } + } + }, + "allOf" : [ { + "$ref" : "#/components/schemas/Reference" + } ] + }, + "Key" : { + "required" : [ "type", "value" ], + "type" : "object", + "properties" : { + "type" : { + "$ref" : "#/components/schemas/KeyElements" + }, + "value" : { + "type" : "string" + } + } + }, + "KeyElements" : { + "type" : "string", + "enum" : [ "AssetAdministrationShell", "AccessPermissionRule", "ConceptDescription", "Submodel", "AnnotatedRelationshipElement", "BasicEvent", "Blob", "Capability", "DataElement", "File", "Entity", "Event", "MultiLanguageProperty", "Operation", "Property", "Range", "ReferenceElement", "RelationshipElement", "SubmodelElement", "SubmodelElementList", "SubmodelElementStruct", "View", "FragmentReference" ] + }, + "AssetAdministrationShell" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Identifiable" + }, { + "$ref" : "#/components/schemas/HasDataSpecification" + }, { + "required" : [ "assetInformation" ], + "properties" : { + "assetInformation" : { + "$ref" : "#/components/schemas/AssetInformation" + }, + "derivedFrom" : { + "$ref" : "#/components/schemas/Reference" + }, + "security" : { + "$ref" : "#/components/schemas/Security" + }, + "submodels" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Reference" + } + }, + "views" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/View" + } + } + } + } ] + }, + "Identifiable" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Referable" + }, { + "required" : [ "identification" ], + "properties" : { + "administration" : { + "$ref" : "#/components/schemas/AdministrativeInformation" + }, + "identification" : { + "$ref" : "#/components/schemas/Identifier" + } + } + } ] + }, + "Referable" : { + "allOf" : [ { + "$ref" : "#/components/schemas/HasExtensions" + }, { + "required" : [ "idShort", "modelType" ], + "properties" : { + "category" : { + "type" : "string" + }, + "description" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/LangString" + } + }, + "displayName" : { + "type" : "string" + }, + "idShort" : { + "type" : "string" + }, + "modelType" : { + "$ref" : "#/components/schemas/ModelType" + } + } + } ] + }, + "HasExtensions" : { + "type" : "object", + "properties" : { + "extensions" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Extension" + } + } + } + }, + "Extension" : { + "allOf" : [ { + "$ref" : "#/components/schemas/HasSemantics" + }, { + "required" : [ "name" ], + "properties" : { + "name" : { + "type" : "string" + }, + "refersTo" : { + "$ref" : "#/components/schemas/Reference" + }, + "value" : { + "type" : "string" + }, + "valueType" : { + "$ref" : "#/components/schemas/ValueTypeEnum" + } + } + } ] + }, + "ValueTypeEnum" : { + "type" : "string", + "enum" : [ "anyUri", "base64Binary", "boolean", "date", "dateTime", "dateTimeStamp", "decimal", "integer", "long", "int", "short", "byte", "nonNegativeInteger", "positiveInteger", "unsignedLong", "unsignedInt", "unsignedShort", "unsignedByte", "nonPositiveInteger", "negativeInteger", "double", "duration", "dayTimeDuration", "yearMonthDuration", "float", "gDay", "gMonth", "gMonthDay", "gYear", "gYearMonth", "hexBinary", "NOTATION", "QName", "string", "normalizedString", "token", "language", "Name", "NCName", "ENTITY", "ID", "IDREF", "NMTOKEN", "time" ] + }, + "LangString" : { + "required" : [ "language", "text" ], + "type" : "object", + "properties" : { + "language" : { + "type" : "string" + }, + "text" : { + "type" : "string" + } + }, + "example" : { + "language" : "language", + "text" : "text" + } + }, + "ModelType" : { + "required" : [ "name" ], + "type" : "object", + "properties" : { + "name" : { + "$ref" : "#/components/schemas/ModelTypes" + } + } + }, + "ModelTypes" : { + "type" : "string", + "enum" : [ "AssetAdministrationShell", "ConceptDescription", "Submodel", "AnnotatedRelationshipElement", "BasicEvent", "Blob", "Capability", "DataElement", "File", "Entity", "Event", "ModelReference", "MultiLanguageProperty", "Operation", "Property", "Range", "ReferenceElement", "RelationshipElement", "SubmodelElement", "SubmodelElementList", "SubmodelElementStruct", "View", "GlobalReference", "FragmentReference", "Constraint", "Formula", "Qualifier" ] + }, + "AdministrativeInformation" : { + "type" : "object", + "properties" : { + "revision" : { + "type" : "string" + }, + "version" : { + "type" : "string" + } + }, + "example" : { + "version" : "version", + "revision" : "revision" + } + }, + "HasDataSpecification" : { + "type" : "object", + "properties" : { + "embeddedDataSpecifications" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/EmbeddedDataSpecification" + } + } + } + }, + "EmbeddedDataSpecification" : { + "required" : [ "dataSpecification", "dataSpecificationContent" ], + "type" : "object", + "properties" : { + "dataSpecification" : { + "$ref" : "#/components/schemas/Reference" + }, + "dataSpecificationContent" : { + "$ref" : "#/components/schemas/DataSpecificationContent" + } + } + }, + "DataSpecificationContent" : { + "oneOf" : [ { + "$ref" : "#/components/schemas/DataSpecificationIEC61360Content" + }, { + "$ref" : "#/components/schemas/DataSpecificationPhysicalUnitContent" + } ] + }, + "DataSpecificationIEC61360Content" : { + "allOf" : [ { + "$ref" : "#/components/schemas/ValueObject" + }, { + "required" : [ "preferredName" ], + "type" : "object", + "properties" : { + "dataType" : { + "type" : "string", + "enum" : [ "DATE", "STRING", "STRING_TRANSLATABLE", "REAL_MEASURE", "REAL_COUNT", "REAL_CURRENCY", "BOOLEAN", "URL", "RATIONAL", "RATIONAL_MEASURE", "TIME", "TIMESTAMP", "INTEGER_COUNT", "INTEGER_MEASURE", "INTEGER_CURRENCY" ] + }, + "definition" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/LangString" + } + }, + "levelType" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/LevelType" + } + }, + "preferredName" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/LangString" + } + }, + "shortName" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/LangString" + } + }, + "sourceOfDefinition" : { + "type" : "string" + }, + "symbol" : { + "type" : "string" + }, + "unit" : { + "type" : "string" + }, + "unitId" : { + "$ref" : "#/components/schemas/Reference" + }, + "valueFormat" : { + "type" : "string" + }, + "valueList" : { + "$ref" : "#/components/schemas/ValueList" + } + } + } ] + }, + "ValueObject" : { + "type" : "object", + "properties" : { + "value" : { + "type" : "string" + }, + "valueId" : { + "$ref" : "#/components/schemas/Reference" + }, + "valueType" : { + "$ref" : "#/components/schemas/ValueTypeEnum" + } + } + }, + "LevelType" : { + "type" : "string", + "enum" : [ "Min", "Max", "Nom", "Typ" ] + }, + "ValueList" : { + "required" : [ "valueReferencePairTypes" ], + "type" : "object", + "properties" : { + "valueReferencePairTypes" : { + "minItems" : 1, + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ValueReferencePairType" + } + } + } + }, + "ValueReferencePairType" : { + "allOf" : [ { + "$ref" : "#/components/schemas/ValueObject" + } ] + }, + "DataSpecificationPhysicalUnitContent" : { + "required" : [ "definition", "unitName", "unitSymbol" ], + "type" : "object", + "properties" : { + "conversionFactor" : { + "type" : "string" + }, + "definition" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/LangString" + } + }, + "dinNotation" : { + "type" : "string" + }, + "eceCode" : { + "type" : "string" + }, + "eceName" : { + "type" : "string" + }, + "nistName" : { + "type" : "string" + }, + "registrationAuthorityId" : { + "type" : "string" + }, + "siName" : { + "type" : "string" + }, + "siNotation" : { + "type" : "string" + }, + "sourceOfDefinition" : { + "type" : "string" + }, + "supplier" : { + "type" : "string" + }, + "unitName" : { + "type" : "string" + }, + "unitSymbol" : { + "type" : "string" + } + } + }, + "AssetInformation" : { + "allOf" : [ { + "required" : [ "assetKind" ], + "properties" : { + "assetKind" : { + "$ref" : "#/components/schemas/AssetKind" + }, + "billOfMaterial" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Reference" + } + }, + "globalAssetId" : { + "$ref" : "#/components/schemas/Reference" + }, + "specificAssetIds" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/IdentifierKeyValuePair" + } + }, + "thumbnail" : { + "$ref" : "#/components/schemas/File" + } + } + } ] + }, + "AssetKind" : { + "type" : "string", + "enum" : [ "Type", "Instance" ] + }, + "File" : { + "allOf" : [ { + "$ref" : "#/components/schemas/SubmodelElement" + }, { + "required" : [ "mimeType" ], + "properties" : { + "mimeType" : { + "type" : "string" + }, + "value" : { + "type" : "string" + } + } + } ] + }, + "SubmodelElement" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Referable" + }, { + "$ref" : "#/components/schemas/HasDataSpecification" + }, { + "$ref" : "#/components/schemas/HasSemantics" + }, { + "$ref" : "#/components/schemas/Qualifiable" + }, { + "properties" : { + "kind" : { + "$ref" : "#/components/schemas/ModelingKind" + } + } + } ], + "oneOf" : [ { + "$ref" : "#/components/schemas/Blob" + }, { + "$ref" : "#/components/schemas/File" + }, { + "$ref" : "#/components/schemas/Capability" + }, { + "$ref" : "#/components/schemas/Entity" + }, { + "$ref" : "#/components/schemas/Event" + }, { + "$ref" : "#/components/schemas/BasicEvent" + }, { + "$ref" : "#/components/schemas/MultiLanguageProperty" + }, { + "$ref" : "#/components/schemas/Operation" + }, { + "$ref" : "#/components/schemas/Property" + }, { + "$ref" : "#/components/schemas/Range" + }, { + "$ref" : "#/components/schemas/ReferenceElement" + }, { + "$ref" : "#/components/schemas/AnnotatedRelationshipElement" + }, { + "$ref" : "#/components/schemas/RelationshipElement" + }, { + "$ref" : "#/components/schemas/SubmodelElementList" + }, { + "$ref" : "#/components/schemas/SubmodelElementStruct" + } ] + }, + "Qualifiable" : { + "type" : "object", + "properties" : { + "qualifiers" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Constraint" + } + } + } + }, + "Constraint" : { + "required" : [ "modelType" ], + "properties" : { + "modelType" : { + "$ref" : "#/components/schemas/ModelType" + } + }, + "oneOf" : [ { + "$ref" : "#/components/schemas/Qualifier" + }, { + "$ref" : "#/components/schemas/Formula" + } ] + }, + "Qualifier" : { + "allOf" : [ { + "$ref" : "#/components/schemas/HasSemantics" + }, { + "$ref" : "#/components/schemas/ValueObject" + }, { + "required" : [ "type" ], + "properties" : { + "type" : { + "type" : "string" + } + } + } ] + }, + "Formula" : { + "allOf" : [ { + "properties" : { + "dependsOn" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Reference" + } + } + } + } ] + }, + "ModelingKind" : { + "type" : "string", + "enum" : [ "Template", "Instance" ] + }, + "Blob" : { + "allOf" : [ { + "$ref" : "#/components/schemas/SubmodelElement" + }, { + "required" : [ "mimeType" ], + "properties" : { + "mimeType" : { + "type" : "string" + }, + "value" : { + "type" : "string" + } + } + } ] + }, + "Capability" : { + "allOf" : [ { + "$ref" : "#/components/schemas/SubmodelElement" + } ] + }, + "Entity" : { + "allOf" : [ { + "$ref" : "#/components/schemas/SubmodelElement" + }, { + "required" : [ "entityType" ], + "properties" : { + "entityType" : { + "$ref" : "#/components/schemas/EntityType" + }, + "globalAssetId" : { + "$ref" : "#/components/schemas/Reference" + }, + "specificAssetIds" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/IdentifierKeyValuePair" + } + }, + "statements" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/SubmodelElement" + } + } + } + } ] + }, + "EntityType" : { + "type" : "string", + "enum" : [ "CoManagedEntity", "SelfManagedEntity" ] + }, + "Event" : { + "allOf" : [ { + "$ref" : "#/components/schemas/SubmodelElement" + } ] + }, + "BasicEvent" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Event" + }, { + "required" : [ "observed" ], + "properties" : { + "observed" : { + "$ref" : "#/components/schemas/Reference" + } + } + } ] + }, + "MultiLanguageProperty" : { + "allOf" : [ { + "$ref" : "#/components/schemas/SubmodelElement" + }, { + "properties" : { + "value" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/LangString" + } + }, + "valueId" : { + "$ref" : "#/components/schemas/Reference" + } + } + } ] + }, + "Operation" : { + "allOf" : [ { + "$ref" : "#/components/schemas/SubmodelElement" + }, { + "properties" : { + "inoutputVariable" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/OperationVariable" + } + }, + "inputVariable" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/OperationVariable" + } + }, + "outputVariable" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/OperationVariable" + } + } + } + } ] + }, + "OperationVariable" : { + "required" : [ "value" ], + "type" : "object", + "properties" : { + "value" : { + "oneOf" : [ { + "$ref" : "#/components/schemas/Blob" + }, { + "$ref" : "#/components/schemas/File" + }, { + "$ref" : "#/components/schemas/Capability" + }, { + "$ref" : "#/components/schemas/Entity" + }, { + "$ref" : "#/components/schemas/Event" + }, { + "$ref" : "#/components/schemas/BasicEvent" + }, { + "$ref" : "#/components/schemas/MultiLanguageProperty" + }, { + "$ref" : "#/components/schemas/Operation" + }, { + "$ref" : "#/components/schemas/Property" + }, { + "$ref" : "#/components/schemas/Range" + }, { + "$ref" : "#/components/schemas/ReferenceElement" + }, { + "$ref" : "#/components/schemas/AnnotatedRelationshipElement" + }, { + "$ref" : "#/components/schemas/RelationshipElement" + }, { + "$ref" : "#/components/schemas/SubmodelElementList" + }, { + "$ref" : "#/components/schemas/SubmodelElementStruct" + } ] + } + }, + "example" : { + "value" : "" + } + }, + "Property" : { + "allOf" : [ { + "$ref" : "#/components/schemas/SubmodelElement" + }, { + "$ref" : "#/components/schemas/ValueObject" + } ] + }, + "Range" : { + "allOf" : [ { + "$ref" : "#/components/schemas/SubmodelElement" + }, { + "required" : [ "valueType" ], + "properties" : { + "max" : { + "type" : "string" + }, + "min" : { + "type" : "string" + }, + "valueType" : { + "$ref" : "#/components/schemas/ValueTypeEnum" + } + } + } ] + }, + "ReferenceElement" : { + "allOf" : [ { + "$ref" : "#/components/schemas/SubmodelElement" + }, { + "properties" : { + "value" : { + "$ref" : "#/components/schemas/Reference" + } + } + } ] + }, + "AnnotatedRelationshipElement" : { + "allOf" : [ { + "$ref" : "#/components/schemas/RelationshipElement" + }, { + "properties" : { + "annotation" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/DataElement" + } + } + } + } ] + }, + "RelationshipElement" : { + "allOf" : [ { + "$ref" : "#/components/schemas/SubmodelElement" + }, { + "required" : [ "first", "second" ], + "properties" : { + "first" : { + "$ref" : "#/components/schemas/Reference" + }, + "second" : { + "$ref" : "#/components/schemas/Reference" + } + } + } ] + }, + "DataElement" : { + "oneOf" : [ { + "$ref" : "#/components/schemas/Blob" + }, { + "$ref" : "#/components/schemas/File" + }, { + "$ref" : "#/components/schemas/MultiLanguageProperty" + }, { + "$ref" : "#/components/schemas/Property" + }, { + "$ref" : "#/components/schemas/Range" + }, { + "$ref" : "#/components/schemas/ReferenceElement" + } ] + }, + "SubmodelElementList" : { + "allOf" : [ { + "$ref" : "#/components/schemas/SubmodelElement" + }, { + "properties" : { + "semanticIdValues" : { + "$ref" : "#/components/schemas/Reference" + }, + "submodelElementTypeValues" : { + "$ref" : "#/components/schemas/ModelType" + }, + "value" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/SubmodelElement" + } + }, + "valueTypeValues" : { + "$ref" : "#/components/schemas/ValueTypeEnum" + } + } + } ] + }, + "SubmodelElementStruct" : { + "properties" : { + "value" : { + "$ref" : "#/components/schemas/SubmodelElement" + } + }, + "allOf" : [ { + "$ref" : "#/components/schemas/SubmodelElement" + } ] + }, + "Security" : { + "required" : [ "accessControlPolicyPoints" ], + "type" : "object", + "properties" : { + "accessControlPolicyPoints" : { + "$ref" : "#/components/schemas/AccessControlPolicyPoints" + }, + "certificate" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Certificate" + } + }, + "requiredCertificateExtension" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Reference" + } + } + } + }, + "AccessControlPolicyPoints" : { + "required" : [ "policyAdministrationPoint", "policyDecisionPoint", "policyEnforcementPoint" ], + "type" : "object", + "properties" : { + "policyAdministrationPoint" : { + "$ref" : "#/components/schemas/PolicyAdministrationPoint" + }, + "policyDecisionPoint" : { + "$ref" : "#/components/schemas/PolicyDecisionPoint" + }, + "policyEnforcementPoint" : { + "$ref" : "#/components/schemas/PolicyEnforcementPoint" + }, + "policyInformationPoints" : { + "$ref" : "#/components/schemas/PolicyInformationPoints" + } + } + }, + "PolicyAdministrationPoint" : { + "required" : [ "externalAccessControl" ], + "type" : "object", + "properties" : { + "externalAccessControl" : { + "type" : "boolean" + }, + "localAccessControl" : { + "$ref" : "#/components/schemas/AccessControl" + } + } + }, + "AccessControl" : { + "type" : "object", + "properties" : { + "accessPermissionRule" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/AccessPermissionRule" + } + }, + "defaultEnvironmentAttributes" : { + "$ref" : "#/components/schemas/Reference" + }, + "defaultPermissions" : { + "$ref" : "#/components/schemas/Reference" + }, + "defaultSubjectAttributes" : { + "$ref" : "#/components/schemas/Reference" + }, + "selectableEnvironmentAttributes" : { + "$ref" : "#/components/schemas/Reference" + }, + "selectablePermissions" : { + "$ref" : "#/components/schemas/Reference" + }, + "selectableSubjectAttributes" : { + "$ref" : "#/components/schemas/Reference" + } + } + }, + "AccessPermissionRule" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Referable" + }, { + "$ref" : "#/components/schemas/Qualifiable" + }, { + "required" : [ "targetSubjectAttributes" ], + "properties" : { + "permissionsPerObject" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PermissionsPerObject" + } + }, + "targetSubjectAttributes" : { + "minItems" : 1, + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/SubjectAttributes" + } + } + } + } ] + }, + "PermissionsPerObject" : { + "type" : "object", + "properties" : { + "object" : { + "$ref" : "#/components/schemas/Reference" + }, + "permission" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Permission" + } + }, + "targetObjectAttributes" : { + "$ref" : "#/components/schemas/ObjectAttributes" + } + } + }, + "Permission" : { + "required" : [ "kindOfPermission", "permission" ], + "type" : "object", + "properties" : { + "kindOfPermission" : { + "type" : "string", + "enum" : [ "Allow", "Deny", "NotApplicable", "Undefined" ] + }, + "permission" : { + "$ref" : "#/components/schemas/Reference" + } + } + }, + "ObjectAttributes" : { + "type" : "object", + "properties" : { + "objectAttribute" : { + "minItems" : 1, + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Property" + } + } + } + }, + "SubjectAttributes" : { + "type" : "object", + "properties" : { + "subjectAttributes" : { + "minItems" : 1, + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Reference" + } + } + } + }, + "PolicyDecisionPoint" : { + "required" : [ "externalPolicyDecisionPoints" ], + "type" : "object", + "properties" : { + "externalPolicyDecisionPoints" : { + "type" : "boolean" + } + } + }, + "PolicyEnforcementPoint" : { + "required" : [ "externalPolicyEnforcementPoint" ], + "type" : "object", + "properties" : { + "externalPolicyEnforcementPoint" : { + "type" : "boolean" + } + } + }, + "PolicyInformationPoints" : { + "required" : [ "externalInformationPoint" ], + "type" : "object", + "properties" : { + "externalInformationPoint" : { + "type" : "boolean" + }, + "internalInformationPoint" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Reference" + } + } + } + }, + "Certificate" : { + "type" : "object", + "oneOf" : [ { + "$ref" : "#/components/schemas/BlobCertificate" + } ] + }, + "BlobCertificate" : { + "properties" : { + "blobCertificate" : { + "$ref" : "#/components/schemas/Blob" + }, + "containedExtension" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Reference" + } + }, + "lastCertificate" : { + "type" : "boolean" + } + } + }, + "View" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Referable" + }, { + "$ref" : "#/components/schemas/HasDataSpecification" + }, { + "$ref" : "#/components/schemas/HasSemantics" + }, { + "properties" : { + "containedElements" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Reference" + } + } + } + } ] + }, + "Submodel" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Identifiable" + }, { + "$ref" : "#/components/schemas/HasDataSpecification" + }, { + "$ref" : "#/components/schemas/Qualifiable" + }, { + "$ref" : "#/components/schemas/HasSemantics" + }, { + "properties" : { + "kind" : { + "$ref" : "#/components/schemas/ModelingKind" + }, + "submodelElements" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/SubmodelElement" + } + } + } + } ] + }, + "OperationRequest" : { + "type" : "object", + "properties" : { + "inoutputArguments" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/OperationVariable" + } + }, + "inputArguments" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/OperationVariable" + } + }, + "requestId" : { + "type" : "string" + }, + "timeout" : { + "type" : "integer" + } + } + }, + "OperationResult" : { + "type" : "object", + "properties" : { + "executionResult" : { + "$ref" : "#/components/schemas/Result" + }, + "executionState" : { + "type" : "string", + "enum" : [ "Initiated", "Running", "Completed", "Canceled", "Failed", "Timeout" ] + }, + "inoutputArguments" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/OperationVariable" + } + }, + "outputArguments" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/OperationVariable" + } + }, + "requestId" : { + "type" : "string" + } + }, + "example" : { + "outputArguments" : [ null, null ], + "requestId" : "requestId", + "executionResult" : { + "success" : true, + "messages" : [ { + "code" : "code", + "messageType" : "Undefined", + "text" : "text", + "timestamp" : "timestamp" + }, { + "code" : "code", + "messageType" : "Undefined", + "text" : "text", + "timestamp" : "timestamp" + } ] + }, + "executionState" : "Initiated", + "inoutputArguments" : [ { + "value" : "" + }, { + "value" : "" + } ] + } + }, + "Result" : { + "type" : "object", + "properties" : { + "messages" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Message" + } + }, + "success" : { + "type" : "boolean" + } + }, + "example" : { + "success" : true, + "messages" : [ { + "code" : "code", + "messageType" : "Undefined", + "text" : "text", + "timestamp" : "timestamp" + }, { + "code" : "code", + "messageType" : "Undefined", + "text" : "text", + "timestamp" : "timestamp" + } ] + } + }, + "Message" : { + "type" : "object", + "properties" : { + "code" : { + "type" : "string" + }, + "messageType" : { + "type" : "string", + "enum" : [ "Undefined", "Info", "Warning", "Error", "Exception" ] + }, + "text" : { + "type" : "string" + }, + "timestamp" : { + "type" : "string" + } + }, + "example" : { + "code" : "code", + "messageType" : "Undefined", + "text" : "text", + "timestamp" : "timestamp" + } + }, + "AssetAdministrationShellDescriptor" : { + "required" : [ "endpoints", "identification" ], + "type" : "object", + "properties" : { + "administration" : { + "$ref" : "#/components/schemas/AdministrativeInformation" + }, + "description" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/LangString" + } + }, + "globalAssetId" : { + "$ref" : "#/components/schemas/Reference" + }, + "idShort" : { + "type" : "string" + }, + "identification" : { + "$ref" : "#/components/schemas/Identifier" + }, + "specificAssetIds" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/IdentifierKeyValuePair" + } + }, + "submodelDescriptors" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/SubmodelDescriptor" + } + } + }, + "example" : { + "identification" : "identification", + "idShort" : "idShort", + "specificAssetIds" : [ "", "" ], + "administration" : { + "version" : "version", + "revision" : "revision" + }, + "description" : [ { + "language" : "language", + "text" : "text" + }, { + "language" : "language", + "text" : "text" + } ], + "submodelDescriptors" : [ { + "semanticId" : null, + "identification" : null, + "idShort" : "idShort", + "administration" : null, + "description" : [ null, null ] + }, { + "semanticId" : null, + "identification" : null, + "idShort" : "idShort", + "administration" : null, + "description" : [ null, null ] + } ], + "globalAssetId" : "" + }, + "allOf" : [ { + "$ref" : "#/components/schemas/Descriptor" + } ] + }, + "Descriptor" : { + "type" : "object", + "properties" : { + "endpoints" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Endpoint" + } + } + }, + "example" : "{ \"endpoints\": [{ \"protocolInformation\": { \"endpointAddress\": \"https://localhost:1234\", \"endpointProtocolVersion: \"1.1\" }, \"interface\": \"AAS-1.0\" }, { \"protocolInformation\": { \"endpointAddress\": \"opc.tcp://localhost:4840\" }, \"interface\": \"AAS-1.0\" }, { \"protocolInformation\": { \"endpointAddress\": \"https://localhost:5678\", \"endpointProtocolVersion: \"1.1\", \"subprotocol\": \"OPC UA Basic SOAP\", \"subprotocolBody\": \"ns=2;s=MyAAS\", \"subprotocolBodyEncoding\": \"plain\" }, \"interface\": \"AAS-1.0\" }] }" + }, + "Endpoint" : { + "required" : [ "interface", "protocolInformation" ], + "type" : "object", + "properties" : { + "interface" : { + "type" : "string" + }, + "protocolInformation" : { + "$ref" : "#/components/schemas/ProtocolInformation" + } + } + }, + "ProtocolInformation" : { + "required" : [ "endpointAddress" ], + "type" : "object", + "properties" : { + "endpointAddress" : { + "type" : "string" + }, + "endpointProtocol" : { + "type" : "string" + }, + "endpointProtocolVersion" : { + "type" : "string" + }, + "subprotocol" : { + "type" : "string" + }, + "subprotocolBody" : { + "type" : "string" + }, + "subprotocolBodyEncoding" : { + "type" : "string" + } + } + }, + "SubmodelDescriptor" : { + "required" : [ "endpoints", "identification" ], + "type" : "object", + "properties" : { + "administration" : { + "$ref" : "#/components/schemas/AdministrativeInformation" + }, + "description" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/LangString" + } + }, + "idShort" : { + "type" : "string" + }, + "identification" : { + "$ref" : "#/components/schemas/Identifier" + }, + "semanticId" : { + "$ref" : "#/components/schemas/Reference" + } + }, + "example" : { + "semanticId" : null, + "identification" : null, + "idShort" : "idShort", + "administration" : null, + "description" : [ null, null ] + }, + "allOf" : [ { + "$ref" : "#/components/schemas/Descriptor" + } ] + }, + "ConceptDescription" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Identifiable" + }, { + "$ref" : "#/components/schemas/HasDataSpecification" + }, { + "properties" : { + "isCaseOf" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Reference" + } + } + } + } ] + }, + "PackageDescription" : { + "type" : "object", + "properties" : { + "aasIds" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Identifier" + } + }, + "packageId" : { + "type" : "string" + } + }, + "example" : { + "aasIds" : [ "aasIds", "aasIds" ], + "packageId" : "packageId" + } + }, + "AssetAdministrationShellEnvironment" : { + "type" : "object", + "properties" : { + "assetAdministrationShells" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/AssetAdministrationShell" + } + }, + "conceptDescriptions" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ConceptDescription" + } + }, + "submodels" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Submodel" + } + } + } + }, + "packages_body" : { + "type" : "object", + "properties" : { + "aasIds" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Identifier" + } + }, + "file" : { + "type" : "string", + "format" : "binary" + }, + "fileName" : { + "type" : "string" + } + } + }, + "packages_packageId_body" : { + "type" : "object", + "properties" : { + "aasIds" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Identifier" + } + }, + "file" : { + "type" : "string", + "format" : "binary" + }, + "fileName" : { + "type" : "string" + } + } + } + } + } +} diff --git a/src/aas-api-webapp-registry/wwwroot/web.config b/src/aas-api-webapp-registry/wwwroot/web.config new file mode 100644 index 0000000..e70a777 --- /dev/null +++ b/src/aas-api-webapp-registry/wwwroot/web.config @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/aas-registry-service-tests/AAS Registry Service Tests.csproj b/src/aas-registry-service-tests/AAS Registry Service Tests.csproj new file mode 100644 index 0000000..760f203 --- /dev/null +++ b/src/aas-registry-service-tests/AAS Registry Service Tests.csproj @@ -0,0 +1,32 @@ + + + + netcoreapp3.1 + AAS_Registry_Service_Tests + + false + + 06b295f3-2368-43ff-b329-5e66787de288 + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/src/aas-registry-service-tests/AASRegistryServiceTests.cs b/src/aas-registry-service-tests/AASRegistryServiceTests.cs new file mode 100644 index 0000000..6be0038 --- /dev/null +++ b/src/aas-registry-service-tests/AASRegistryServiceTests.cs @@ -0,0 +1,121 @@ +using AAS.API.Models; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using StackExchange.Redis; +using System; +using System.Collections.Generic; + +namespace AAS.API.Registry.Tests +{ + [TestClass] + public class AASRegistryServiceTests + { + private IConfiguration configuration; + + private IDistributedCache cache; + + private AASRegistry registryService; + + public AASRegistryServiceTests() + { + configuration = new ConfigurationBuilder().AddJsonFile("appsettings.tests.json").Build(); + } + + [TestInitialize] + public void Setup() + { + var builder = new HostBuilder() + .ConfigureAppConfiguration((hostContext, configBuilder) => + { configBuilder.AddJsonFile("appsettings.tests.json").AddUserSecrets(); }) + .ConfigureServices((hostContext, services) => + { + services.AddStackExchangeRedisCache(setupAction => + { + setupAction.Configuration = hostContext.Configuration.GetConnectionString("RedisCache"); + }); + services.AddSingleton(); + configuration = hostContext.Configuration; + }).UseConsoleLifetime(); + + var host = builder.Build(); + + registryService = host.Services.GetService(); + cache = host.Services.GetService(); + } + + [TestMethod] + public void TestDISetup() + { + Assert.IsNotNull(registryService); + } + + [TestMethod] + public void TestCreateEntryForFesto01() + { + AssetAdministrationShellDescriptor aasDesc = CreateAASDescriptorForFesto01(); + + try + { + AssetAdministrationShellDescriptor result = registryService.CreateAssetAdministrationShellDescriptor(aasDesc).GetAwaiter().GetResult(); + Assert.IsNotNull(result); + Assert.AreEqual(aasDesc, result); + + Console.WriteLine(cache.GetString($"aas_{aasDesc.Identification}")); + } + finally + { + try + { + cache.RemoveAsync($"aas_{aasDesc.Identification}").GetAwaiter().GetResult(); + } + catch (Exception) { } + } + } + + [TestMethod] + public void TestDeleteEntryForDemo() + { + string key = Guid.NewGuid().ToString(); + cache.SetString($"aas_{key}", "Test"); + + try + { + registryService.DeleteAssetAdministrationShellDescriptorById(key).GetAwaiter().GetResult(); + } + catch (Exception ex) + { + try + { + cache.RemoveAsync($"aas_{key}").GetAwaiter().GetResult(); + } + catch (Exception) { } + + throw ex; + } + } + + private AssetAdministrationShellDescriptor CreateAASDescriptorForFesto01() + { + AssetAdministrationShellDescriptor result = new AssetAdministrationShellDescriptor(); + + result.Identification = "test_smart.festo.com/demo/aas/1/1/454576463545648365874"; + result.IdShort = "Festo_3S7PM0CP4BD"; + result.SubmodelDescriptors = new List(); + result.SubmodelDescriptors.Add(new SubmodelDescriptor() + { + IdShort = "Nameplate", + Identification = "www.company.com/ids/sm/4343_5072_7091_3242", + SemanticId = new GlobalReference() { Value = new List() { "https://www.hsu-hh.de/aut/aas/nameplate" } } + }); + result.Endpoints = new List(); + result.Endpoints.Add(new Endpoint() + { _Interface = "AAS-1.0", + ProtocolInformation = new ProtocolInformation() { EndpointAddress = "https://hack2021aasapi.azurewebsites.net", EndpointProtocolVersion = "1.1"} }); + + return result; + } + } +} diff --git a/src/aas-registry-service-tests/appsettings.tests.json b/src/aas-registry-service-tests/appsettings.tests.json new file mode 100644 index 0000000..c6af7d9 --- /dev/null +++ b/src/aas-registry-service-tests/appsettings.tests.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Information", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/src/aas-registry-service/AAS Registry Service.csproj b/src/aas-registry-service/AAS Registry Service.csproj new file mode 100644 index 0000000..64cb190 --- /dev/null +++ b/src/aas-registry-service/AAS Registry Service.csproj @@ -0,0 +1,18 @@ + + + + Exe + netcoreapp3.1 + AAS_Registry_Service + + + + + + + + + + + + diff --git a/src/aas-registry-service/AASRegistry.cs b/src/aas-registry-service/AASRegistry.cs new file mode 100644 index 0000000..0c65c85 --- /dev/null +++ b/src/aas-registry-service/AASRegistry.cs @@ -0,0 +1,39 @@ +using AAS.API.Services; +using System; + +namespace AAS.API.Registry +{ + public interface AASRegistry : AAS.API.Interfaces.Registry + { + } + public class AASRegistryException : AASServiceException + { + // + // Summary: + // Initializes a new instance of the Azure.RequestFailedException class with a specified + // error message. + // + // Parameters: + // message: + // The message that describes the error. + public AASRegistryException(string message) : base(message) + { + } + // + // Summary: + // Initializes a new instance of the Azure.RequestFailedException class with a specified + // error message, HTTP status code and a reference to the inner exception that is + // the cause of this exception. + // + // Parameters: + // message: + // The error message that explains the reason for the exception. + // + // innerException: + // The exception that is the cause of the current exception, or a null reference + // (Nothing in Visual Basic) if no inner exception is specified. + public AASRegistryException(string message, Exception? innerException) : base(message, innerException) + { + } + } +} diff --git a/src/aas-registry-service/Cache.cs b/src/aas-registry-service/Cache.cs new file mode 100644 index 0000000..738dd35 --- /dev/null +++ b/src/aas-registry-service/Cache.cs @@ -0,0 +1,38 @@ +using Microsoft.Extensions.Caching.Distributed; +using Newtonsoft.Json; +using System.Threading.Tasks; + +namespace AAS.API.Registry +{ + public class Cache + { + private readonly IDistributedCache _cache; + + public Cache(IDistributedCache aCache) + { + _cache = aCache; + } + + public async Task Get(string key) where T : class + { + var cachedResponse = await _cache.GetStringAsync(key); + return cachedResponse == null ? null : JsonConvert.DeserializeObject(cachedResponse, + new JsonSerializerSettings() { + ReferenceLoopHandling = ReferenceLoopHandling.Ignore + }); + } + + public async Task Set(string key, T value, DistributedCacheEntryOptions options) where T : class + { + var response = JsonConvert.SerializeObject(value,new JsonSerializerSettings() { + ReferenceLoopHandling = ReferenceLoopHandling.Ignore + }); + await _cache.SetStringAsync(key, response, options); + } + + public async Task Clear(string key) + { + await _cache.RemoveAsync(key); + } + } +} diff --git a/src/aas-registry-service/Program.cs b/src/aas-registry-service/Program.cs new file mode 100644 index 0000000..3a7d904 --- /dev/null +++ b/src/aas-registry-service/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace AAS.API.Registry +{ + internal class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/src/aas-registry-service/RedisImpl/RedisAASRegistry.cs b/src/aas-registry-service/RedisImpl/RedisAASRegistry.cs new file mode 100644 index 0000000..125c999 --- /dev/null +++ b/src/aas-registry-service/RedisImpl/RedisAASRegistry.cs @@ -0,0 +1,176 @@ +using AAS.API.Models; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace AAS.API.Registry +{ + public class RedisAASRegistry : AASRegistry + { + private readonly ILogger _logger; + + private readonly Cache _cache; + + public RedisAASRegistry(IDistributedCache cacheProvider, ILogger log) + { + _logger = log; + + _cache = new Cache(cacheProvider); + } + + public async Task CreateAssetAdministrationShellDescriptor(AssetAdministrationShellDescriptor aasDesc) + { + if (aasDesc == null) + { + if (_logger != null) + _logger.LogError($"Parameter 'aasDesc' must not be null"); + + throw new AASRegistryException($"Parameter 'aasDesc' must not be null", new ArgumentNullException(nameof(aasDesc))); + } + + if (_cache == null) + { + if (_logger != null) + _logger.LogError("Wrong DI configuration. No '_cache' configured"); + + throw new AASRegistryException("Wrong DI configuration. No '_cache' configured"); + } + + if (_logger != null) + _logger.LogTrace($"CreateAssetAdministrationShellDescriptor called for AAS Descriptor with id '{aasDesc.Identification}'"); + + try + { + await _cache.Set($"aas_{aasDesc.Identification}", aasDesc, new DistributedCacheEntryOptions()); + + } catch (Exception ex) + { + if (_logger != null) + _logger.LogError($"Exception while setting a value in the cache: {ex.Message}"); + + throw new AASRegistryException("Exception while setting a value in the cache", ex); + } + + return aasDesc; + } + + public async Task DeleteAssetAdministrationShellDescriptorById(string aasIdentifier) + { + if (string.IsNullOrEmpty(aasIdentifier)) + { + if (_logger != null) + _logger.LogError($"Parameter 'aasIdentifier' must not be empty"); + + throw new AASRegistryException($"Parameter 'aasDesc' must not be empty", new ArgumentNullException(nameof(aasIdentifier))); + } + + if (_cache == null) + { + if (_logger != null) + _logger.LogError("Wrong DI configuration. No '_cache' configured"); + + throw new AASRegistryException("Wrong DI configuration. No '_cache' configured"); + } + + if (_logger != null) + _logger.LogTrace($"DeleteAssetAdministrationShellDescriptorById called for id '{aasIdentifier}'"); + + try + { + await _cache.Clear($"aas_{aasIdentifier}"); + + } catch (Exception ex) + { + if (_logger != null) + _logger.LogError($"Exception while removing '{aasIdentifier}' from the cache: {ex.Message}"); + + throw new AASRegistryException("Exception while removing a value from the cache", ex); + } + } + + public async Task> GetAllAssetAdministrationShellDescriptors() + { + throw new NotImplementedException(); + } + + public async Task GetAssetAdministrationShellDescriptorById(string aasIdentifier) + { + if (string.IsNullOrEmpty(aasIdentifier)) + { + if (_logger != null) + _logger.LogError($"Parameter 'aasIdentifier' must not be empty"); + + throw new AASRegistryException($"Parameter 'aasDesc' must not be empty", new ArgumentNullException(nameof(aasIdentifier))); + } + + if (_cache == null) + { + if (_logger != null) + _logger.LogError("Wrong DI configuration. No '_cache' configured"); + + throw new AASRegistryException("Wrong DI configuration. No '_cache' configured"); + } + + if (_logger != null) + _logger.LogTrace($"GetAssetAdministrationShellDescriptorById called with id '{aasIdentifier}'"); + + try + { + return await _cache.Get($"aas_{aasIdentifier}"); + + } + catch (Exception ex) + { + if (_logger != null) + _logger.LogError($"Exception while reading descriptor for id '{aasIdentifier}' from the cache: {ex.Message}"); + + throw new AASRegistryException($"Exception while reading descriptor for id '{aasIdentifier}' from the cache", ex); + } + } + + public async Task UpdateAssetAdministrationShellDescriptorById(AssetAdministrationShellDescriptor aasDesc, string aasIdentifier) + { + if (aasDesc == null) + { + if (_logger != null) + _logger.LogError($"Parameter 'aasDesc' must not be null"); + + throw new AASRegistryException($"Parameter 'aasDesc' must not be null", new ArgumentNullException(nameof(aasDesc))); + } + + if (string.IsNullOrEmpty(aasIdentifier)) + { + if (_logger != null) + _logger.LogError($"Parameter 'aasIdentifier' must not be empty"); + + throw new AASRegistryException($"Parameter 'aasDesc' must not be empty", new ArgumentNullException(nameof(aasIdentifier))); + } + + if (_cache == null) + { + if (_logger != null) + _logger.LogError("Wrong DI configuration. No '_cache' configured"); + + throw new AASRegistryException("Wrong DI configuration. No '_cache' configured"); + } + + if (_logger != null) + _logger.LogTrace($"UpdateAssetAdministrationShellDescriptorById called for AAS Descriptor with id '{aasIdentifier}'"); + + try + { + await _cache.Set(aasIdentifier, aasDesc, new DistributedCacheEntryOptions()); + + } + catch (Exception ex) + { + if (_logger != null) + _logger.LogError($"Exception while updating '{aasIdentifier}' from the cache: {ex.Message}"); + + throw new AASRegistryException("Exception while updating a value in the cache", ex); + } + } + } +} From fed7c29d0c311cfdc520d79039e7a3057b0e4a34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Mayrb=C3=A4url?= Date: Wed, 19 Jan 2022 19:30:40 +0100 Subject: [PATCH 2/5] Implemented main Registry service operations --- .../build-and-publish-docker-images.yml | 15 +++ README.md | 8 +- ...AdministrationShellRegistryInterfaceApi.cs | 91 +++++++++++-------- 3 files changed, 73 insertions(+), 41 deletions(-) diff --git a/.github/workflows/build-and-publish-docker-images.yml b/.github/workflows/build-and-publish-docker-images.yml index bb513c2..00664b1 100644 --- a/.github/workflows/build-and-publish-docker-images.yml +++ b/.github/workflows/build-and-publish-docker-images.yml @@ -60,6 +60,21 @@ jobs: - name: Image digest for Discovery Server run: echo ${{ steps.docker_build.outputs.digest }} + - name: Build image for Registry Server and push to Docker Hub + uses: docker/build-push-action@v2 + with: + # relative path to the place where source code with Dockerfile is located + context: . + file: src/aas-api-webapp-registry/Dockerfile + # Note: tags has to be all lower-case + tags: | + jmayrbaeurl/aas-registry-server:latest + # build on feature branches, push only on main branch + push: ${{ github.ref == 'refs/heads/master' }} + + - name: Image digest for Registry Server + run: echo ${{ steps.docker_build.outputs.digest }} + - name: Build image for Full Server and push to Docker Hub uses: docker/build-push-action@v2 with: diff --git a/README.md b/README.md index 95ed8e5..7d98270 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,9 @@ using [Azure Blob Storage](https://azure.microsoft.com/en-us/services/storage/bl in a container called `aasxfiles`. The name of the container to be used can be configured. If the container doesn't exist in the Blob storage account, it will be created automatically. For each package a folder with the file name without the file extension will be created in the container. Samples from [AASX Browser](https://admin-shell-io.com/5001/) were used for testing. -- **AAS Discovery server**: See 'aas-api-webapp-discovery' folder in 'src'. Not implemented yet. -- **AAS Shell Repository server**: See 'aas-api-webapp-repository' folder in 'src'. +- **AAS Discovery server**: See 'aas-api-webapp-discovery' folder in 'src'. Partially implemented. +- **AAS Registry server**: See 'aas-api-webapp-registry' folder in 'src'. Partially implemented. +- **AAS Shell Repository server**: See 'aas-api-webapp-repository' folder in 'src'. Partially implemented. - **AAS Full server**: See 'aas-api-webapp-full' folder in 'src'. Implementation of the entire interface collection as part of [Details of the Asset Administration Shell Part 2](https://www.plattform-i40.de/IP/Redaktion/EN/Downloads/Publikation/Details_of_the_Asset_Administration_Shell_Part2_V1.pdf) @@ -51,6 +52,9 @@ to the AASX File server. E.g. by leveraging its Managed Identity of the App serv ## AAS Discovery server TBD +## AAS Registry server +TBD + ## AAS Shell Repository server TBD diff --git a/src/aas-api-webapp-registry/Controllers/AssetAdministrationShellRegistryInterfaceApi.cs b/src/aas-api-webapp-registry/Controllers/AssetAdministrationShellRegistryInterfaceApi.cs index d7c0465..109da8c 100644 --- a/src/aas-api-webapp-registry/Controllers/AssetAdministrationShellRegistryInterfaceApi.cs +++ b/src/aas-api-webapp-registry/Controllers/AssetAdministrationShellRegistryInterfaceApi.cs @@ -51,13 +51,21 @@ public AssetAdministrationShellRegistryInterfaceApiController(ILogger /// Deletes a Submodel Descriptor, i.e. de-registers a submodel /// @@ -75,6 +83,7 @@ public virtual IActionResult DeleteSubmodelDescriptorByIdAASRegistry([FromRoute] throw new NotImplementedException(); } + */ /// /// Returns all Asset Administration Shell Descriptors @@ -87,17 +96,11 @@ public virtual IActionResult DeleteSubmodelDescriptorByIdAASRegistry([FromRoute] [SwaggerResponse(statusCode: 200, type: typeof(List), description: "Requested Asset Administration Shell Descriptors")] public virtual IActionResult GetAllAssetAdministrationShellDescriptors() { - //TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ... - // return StatusCode(200, default(List)); - string exampleJson = null; - exampleJson = "[ {\n \"identification\" : \"identification\",\n \"idShort\" : \"idShort\",\n \"specificAssetIds\" : [ \"\", \"\" ],\n \"administration\" : {\n \"version\" : \"version\",\n \"revision\" : \"revision\"\n },\n \"description\" : [ {\n \"language\" : \"language\",\n \"text\" : \"text\"\n }, {\n \"language\" : \"language\",\n \"text\" : \"text\"\n } ],\n \"submodelDescriptors\" : [ {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n }, {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n } ],\n \"globalAssetId\" : \"\"\n}, {\n \"identification\" : \"identification\",\n \"idShort\" : \"idShort\",\n \"specificAssetIds\" : [ \"\", \"\" ],\n \"administration\" : {\n \"version\" : \"version\",\n \"revision\" : \"revision\"\n },\n \"description\" : [ {\n \"language\" : \"language\",\n \"text\" : \"text\"\n }, {\n \"language\" : \"language\",\n \"text\" : \"text\"\n } ],\n \"submodelDescriptors\" : [ {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n }, {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n } ],\n \"globalAssetId\" : \"\"\n} ]"; - - var example = exampleJson != null - ? JsonConvert.DeserializeObject>(exampleJson) - : default(List); //TODO: Change the data returned - return new ObjectResult(example); + // Currently not supported. Redis Cache doesn't have 'list all' operations + return StatusCode(200, default(List)); } + /* /// /// Returns all Submodel Descriptors /// @@ -120,6 +123,7 @@ public virtual IActionResult GetAllSubmodelDescriptorsAASRegistry([FromRoute][Re : default(List); //TODO: Change the data returned return new ObjectResult(example); } + */ /// /// Returns a specific Asset Administration Shell Descriptor @@ -132,22 +136,19 @@ public virtual IActionResult GetAllSubmodelDescriptorsAASRegistry([FromRoute][Re [SwaggerOperation("GetAssetAdministrationShellDescriptorById")] [SwaggerResponse(statusCode: 200, type: typeof(AssetAdministrationShellDescriptor), description: "Requested Asset Administration Shell Descriptor")] public virtual IActionResult GetAssetAdministrationShellDescriptorById([FromRoute][Required]string aasIdentifier) - { - //TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ... - // return StatusCode(200, default(AssetAdministrationShellDescriptor)); - /* - string exampleJson = null; - exampleJson = "{\n \"identification\" : \"identification\",\n \"idShort\" : \"idShort\",\n \"specificAssetIds\" : [ \"\", \"\" ],\n \"administration\" : {\n \"version\" : \"version\",\n \"revision\" : \"revision\"\n },\n \"description\" : [ {\n \"language\" : \"language\",\n \"text\" : \"text\"\n }, {\n \"language\" : \"language\",\n \"text\" : \"text\"\n } ],\n \"submodelDescriptors\" : [ {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n }, {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n } ],\n \"globalAssetId\" : \"\"\n}"; - - var example = exampleJson != null - ? JsonConvert.DeserializeObject(exampleJson) - : default(AssetAdministrationShellDescriptor); //TODO: Change the data returned - return new ObjectResult(example); - */ + { + _logger.LogInformation($"GetAssetAdministrationShellDescriptorById called for Asset identifier '{aasIdentifier}'"); + + if (registryService == null) + { + _logger.LogError("Invalid setup. No Registry service configured. Check DI setup"); + throw new AASRegistryException("Invalid setup. No Registry service configured. Check DI setup"); + } return new ObjectResult(registryService.GetAssetAdministrationShellDescriptorById(HttpUtility.UrlDecode(aasIdentifier)).GetAwaiter().GetResult()); } + /* /// /// Returns a specific Submodel Descriptor /// @@ -171,6 +172,7 @@ public virtual IActionResult GetSubmodelDescriptorByIdAASRegistry([FromRoute][Re : default(SubmodelDescriptor); //TODO: Change the data returned return new ObjectResult(example); } + */ /// /// Creates a new Asset Administration Shell Descriptor, i.e. registers an AAS @@ -183,18 +185,19 @@ public virtual IActionResult GetSubmodelDescriptorByIdAASRegistry([FromRoute][Re [SwaggerOperation("PostAssetAdministrationShellDescriptor")] [SwaggerResponse(statusCode: 201, type: typeof(AssetAdministrationShellDescriptor), description: "Asset Administration Shell Descriptor created successfully")] public virtual IActionResult PostAssetAdministrationShellDescriptor([FromBody]AssetAdministrationShellDescriptor body) - { - //TODO: Uncomment the next line to return response 201 or use other options such as return this.NotFound(), return this.BadRequest(..), ... - // return StatusCode(201, default(AssetAdministrationShellDescriptor)); - string exampleJson = null; - exampleJson = "{\n \"identification\" : \"identification\",\n \"idShort\" : \"idShort\",\n \"specificAssetIds\" : [ \"\", \"\" ],\n \"administration\" : {\n \"version\" : \"version\",\n \"revision\" : \"revision\"\n },\n \"description\" : [ {\n \"language\" : \"language\",\n \"text\" : \"text\"\n }, {\n \"language\" : \"language\",\n \"text\" : \"text\"\n } ],\n \"submodelDescriptors\" : [ {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n }, {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n } ],\n \"globalAssetId\" : \"\"\n}"; - - var example = exampleJson != null - ? JsonConvert.DeserializeObject(exampleJson) - : default(AssetAdministrationShellDescriptor); //TODO: Change the data returned - return new ObjectResult(example); + { + _logger.LogInformation($"PostAssetAdministrationShellDescriptor called for AAS with identifier '{body.Identification}'"); + + if (registryService == null) + { + _logger.LogError("Invalid setup. No Registry service configured. Check DI setup"); + throw new AASRegistryException("Invalid setup. No Registry service configured. Check DI setup"); + } + + return StatusCode(201, registryService.CreateAssetAdministrationShellDescriptor(body).GetAwaiter().GetResult()); } + /* /// /// Creates a new Submodel Descriptor, i.e. registers a submodel /// @@ -218,6 +221,7 @@ public virtual IActionResult PostSubmodelDescriptorAASRegistry([FromBody]Submode : default(SubmodelDescriptor); //TODO: Change the data returned return new ObjectResult(example); } + */ /// /// Updates an existing Asset Administration Shell Descriptor @@ -230,13 +234,21 @@ public virtual IActionResult PostSubmodelDescriptorAASRegistry([FromBody]Submode [ValidateModelState] [SwaggerOperation("PutAssetAdministrationShellDescriptorById")] public virtual IActionResult PutAssetAdministrationShellDescriptorById([FromBody]AssetAdministrationShellDescriptor body, [FromRoute][Required]string aasIdentifier) - { - //TODO: Uncomment the next line to return response 204 or use other options such as return this.NotFound(), return this.BadRequest(..), ... - // return StatusCode(204); + { + _logger.LogInformation($"PutAssetAdministrationShellDescriptorById called for AAS with identifier '{body.Identification}'"); - throw new NotImplementedException(); + if (registryService == null) + { + _logger.LogError("Invalid setup. No Registry service configured. Check DI setup"); + throw new AASRegistryException("Invalid setup. No Registry service configured. Check DI setup"); + } + + registryService.UpdateAssetAdministrationShellDescriptorById(body, HttpUtility.UrlDecode(aasIdentifier)).GetAwaiter().GetResult(); + + return StatusCode(204); } + /* /// /// Updates an existing Submodel Descriptor /// @@ -255,5 +267,6 @@ public virtual IActionResult PutSubmodelDescriptorByIdAASRegistry([FromBody]Subm throw new NotImplementedException(); } + */ } } From 4282c90cebc5cba88e7ac9d621af4ba188069a21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Mayrb=C3=A4url?= Date: Thu, 20 Jan 2022 11:54:45 +0100 Subject: [PATCH 3/5] Added Azure deployment scripts for basic Azure services --- .../images/AzureIIoTHeroscenarioRefArch.png | Bin 0 -> 107875 bytes README.md | 2 + .../aasapiservicestdroles.json | 11 +++++ .../createAzureDigitalTwinsInstance.ps1 | 22 ++++++++++ ...eateAzureRedisCacheDBForRegistryServer.ps1 | 40 ++++++++++++++++++ .../createWebApiAppForService.ps1 | 34 +++++++++++++++ 6 files changed, 109 insertions(+) create mode 100644 Assets/images/AzureIIoTHeroscenarioRefArch.png create mode 100644 scripts/azuredeployment/aasapiservicestdroles.json create mode 100644 scripts/azuredeployment/createWebApiAppForService.ps1 diff --git a/Assets/images/AzureIIoTHeroscenarioRefArch.png b/Assets/images/AzureIIoTHeroscenarioRefArch.png new file mode 100644 index 0000000000000000000000000000000000000000..395bbb1d587583ad0ed39ff9cc37f75210f5f348 GIT binary patch literal 107875 zcmeEtWmH_-)@30fXmGb+!69fM6z=Zs?(V_eogl&8-CYZp1a~JS!6CSHLGFF;zV5I8 z^pBP?y2eO2$tmjWJ@=Bi=2{gdCnJgkj|UF`0FcDRgcJaPSKv#?udtBdAAH0T5a1sW zjtZgzfXWGipWuH$oAOKZ0{}HK2>1HWZ-uiHQ+EUa-t;~Hff%$cHUa>i3dDu@mE3d= zGG6Op2rmzwtN);%35?B@WEfuEbtoZsl2a0bfS}Ixg{R4e^?z%SUrd#|z$Y(Pvtfyy z4~Hd`uf`|;kt8RW&-W{2Ao+U27&F7NB16mQgU|6$8yA}$SP`kn>dZsjNvvr%e;wD8DHI*2*|nvfB!Zo`2{eWIyc;fO%0 zCjSOrdV1-FD%jHfUh%!z9KqKvEm2oEj#zgWnLAcA%N*Vwh?%za`=3 z&d_F7-QSy%;eeU;d3w0gZgVlF4!b;D_PLl<#ZxIY3pB2i!h5^g=E_*264C0sZ(?eS z;9eP=MHV8mn9U#Xc(X@LBK#AziH3qA=n)iJW*1q7!N5hd8lm89_^NVyxyhE8%>qLg z8ZHXA5V0?Hr08cv@+3TV^zeqDW(B$$V@XDH53X`#h$bTpBjB@O0lXehbr=!%C)gSr z;YCjs>vVeGy~!+H3c`tB#yAH|H74w7bK|&>PPeVhtk`G+Q2+_KfGNi{cviJt zZq$Hwy{aTW8{U`7*t6AN?kR9;ZuGH~TAv;)_-x+9lVJHb?O5PPueWDK+DYi}>;Hs#PPBiFHDp@f! zG1L6AMc&s|sidOOsNSmNB9rK2R3bmY-;ZX-DhnK05PVG+buUG+E$>01hXb^1_>QY? zNzR6L=C=AseK@Ocn2Jdy^tl_4j(B?uLZ{RQgL1C(V|evJqK-8QnHm)NUM_o7sBGZI zZnDc!?iGuUA;W7&WgKE%r zhOqJ7>1V7O`NdfAHPIGGG9x>pbAfweL0^F~ru=d-r;6F`sTQd7Vka$XYYzS{AFLMM znj;Ikk{F5Ml{5k+zfx&6=^WBv#3+zCS9NMUi5-MwPZUKPF9$XX4ulsNg(pXn)S!2M zKdqzEyaPpHXcjhqJZ%xP;>#0UAaTE~b(Rm(v+Lc&$;zfmb&+?#h>}}oh6~6T_@X2a zsaC=5G!mXv$T3{~n$G#W_G~sXoUnqAi;~93h+5&1Ur4keZSYLNkHvJtXn1BKvB*f- zk`bD&Bd%`FEHkb_WC}A7mk!GTcFyYV7aE!3!@lkaf@zM6-9lAx~okd zeAU&=3fh#acH^PPq1HuRy-045&~Y#~)r$r)MWAX18LX=$X4F;Yu|C{g?U}>UM6psz zQrn{Ow~cujv{{_<;ld!;a^}=9;2cl^0P^CHDaewY3&rD0XbS{1uCC;DP$V&?OcYRE z&Cu*E2WuT^kGqZ^>5Lx5KWHi-&_;zDP=o0Ab|VsMmMa3=!AAcr^tdcZq{3(B zmft!f;jPXkYykF`4VOoqx|!Pp9{waO@D@1BjFzk9e^uVcv-=-=yI%Cj^N^mKTiqUxHcoSjjIImD!vXTO6x zYS&4yZAwEN6Zjwn!7cA&&>cHiRzN;eNW?B}HfDAP z8gTQ7oOCUqSG;%S=N#DI9V(La=A5a-&PfqjGYqdDDE?gK0*|N?#J&ZC(|(Kh&}X4! zg-okum^$BKgcrmzvgjRkjz&g|fQP#qGO{9Et|O5h5iDP`6eY`1AU){~CoYQXw6)|K zpnek%GfFl(XKrzs4O1o@eIKw4Lmw6UVNy>ADUZe!uAo*t`1G7D6qq+~^NW4*I+dnS ziHj@1v|<@3P*LpAm@faltPd@8G(XqOb^HsPQu2b?BIg;2dZ`VocXT+;PaWZ+P~-eF z@k#Xi7ANs2kr7p)arEt>wq(6o+$?#}kVmo7-^GQ5Dp`x|tenfekK1HghGTVbK|({-W%^xLUsK+xiIAOV19dW! z_1kwXs7urY4y8E<_SB<2$ETf{VO@sFF-tNUpuI`)ZiU8`>dRt@jhR|ibt`=82_f0q zcFlB|wQU2aOz+q#@4!UtIoV6*Y4lU(#>qPr1GW3*9>XKmj7`n;S(7f4H|X<~ngmK#sD@wQj@(UKF)-1km%0y{BAC~%c!32`7+tw;Y|7;ee^Y#( zN>#qyc9KXn`^nGTdlikxXN-hOCRF3j5W(>b6=qV2DOj!^J;2mD-(ARj9xs5Na>U)a zAIVU;+8S#Ti;%s4XJk<91KPl(JgQ20BcHMGDGW<}GVdG7LhVk{)?^(9-%-xIkr%^q z^NU-tmt{1`QAVMO#65ij25Npdbo!5ITU6g~Z_`lwxSh%IKoK+iMKGSWT3 zGMK^vf;d9FtOS#bHxl#UaxuKf~{&rDbP zMn2%hoR<4;`CX$ZF&I;ihS7Gdx`3IwZQ=>wjYE zdlkefoW_fjj;@$nKxrYl8!qf7s9JxWg1w2Q5Pt&ir2sQ+)Z>u!8On9VNs4G znK|emQpYzZl3!{V&>O|wZTYx`&{K&)8)97tQ#TMX%BcJSk5^PUaxDCSj$9b`0Bg=_ z)r2ZnJE(tXls6BrDO^GOZ2%#Np&wjJ^xJADt;?%g#`n1UzBdyq(7={>@i!Ul?r^fK zJdRgX6K4c}ysboz=i&!xmS9wqXobQy{X7R5j3i$}D0a#e!vcYK?Gkoe&z=`j{nLH< zDz8(q(l389-cpq~XfVm8^1Em}<*gr`lxcE*hD2vgIPgB#(kWCU6?00sAh#Y}2KOBm z=RGOj2LS;h^+p+%1+i!YLp73k@2_St(C+7m-)G;KIwZ-_q#5Dx>T=TAy2HfT91ei( zq~IlTlMT<5QknjN!k{Fs-4kn)0p&AeiE0hgE*OcF61ic#t=J1+BQ(7@3l`*%}r8g_r5 z!WH8<(9bAC4>K%c3g=wpaj7)a(}jg+yI7gVw&m0@k<2#7D17I~L|A!GVw_i*xW}%X zpgp%-V1wcsZ!5NB8~n4WbRk~mS$2erX%x>Pv#NF8{-Rw1M<0q)3+tq+MLa@MUZoms zBZ4`P0m}5LLe)TuJO_!34|v;2C9CF3MQ=rGm=53-+OIJXHSl_t-eww&m=_3vmN@0* z$s}cho}CCx3P6cxmi_PD=32+eW=c066e-Hs1Jbw~gtTBmiq2=)2B-zUM7C8$k>&&} z(ze=w3qR50d6Wny!mAT4=yT5TJ~|awJF;0QqaPgnK(PhvXPu1|pApe2ua|1SCA;iH zx&UG8V&p2xl`ma`slf;GhNRIlT4hm)jZF?!KpDLU0XrQCYGH_+LY=ZWNx@4xc>^hL zQM7$Q?tZ1kky82#`N3!`Bm00U*mUaItEpp3w~ymLC+h7GP}jO1uOVPF?4VPnnNmlg zGOFbH;I#K3HbfLhZJmwX>9%i;+=XnIl2h|X8ekIK$m_IS&d&cJUQ@D%OU%$^W7}tQ z7{<8sdngah5awKFj4>ToIdC~``QZ|Jjlvr-QOKtsWi7izs00wEP))+yO z@5C1NTk6@H#B8(Nv!Pn&FO7rLV4R^Nu(Mtr6I)A??M$Tiv>zm5MwO;h3~jq#B{>>V z_kOCn4-m_9FW^=bA8GhxQy=F&XGWBx@~X!=MVUWHb*Xe*&w78Uf%da((c69if}Fs8HN?VT1Yc_Y{?fxYoHNM{n%Ri1NlW!hSyi&ljW*Og1md_&l2 z3gaABBZg-6>F*t)UnfyP5X}gj%=KJ)UTJMCT zge%)&#Nf5uE8X0Ji z%S8^)Qn}yv(OQ3U`9eWS0L(>`B;$){k}@$pj~1qkDUhd*aT-7%zlO3 zCFCNjV&e*6C&gMu*h66t|JXTQJT?Q5#Ywrd$D|&nQ>eAlMCmw6leE?S7BfX{#S7Gj zEb(t`A_+jrX2nRR!o{AC(<*RFEI1iK8X~79HJ6rIVsnH>mi+#TW z7bkV8LD^w%1=rON)@nc& z8t<6I@U72NyLxriah!3oZLLQ^c;TZgU0=^kEBj5Qi! zO4=l~;IKX%t=m&cB7CZWRiJ`$ly`(?ZX)a~hK_B7GZY7JoW;8kZ;wG276)chqv?vZ z6lJesOgXKOPFI0rc4$l|S2k3^IZwl2l_j2!ve^IxuOL+J>3{Y{;bwp&hdq5ZHrcis zihBEM-Dk4uElJkx$ossJxGOdDyGEePgbe>wmQuEn%}jH-0ap=p_&Fd?DcHzJo5~s- zIfgkNK6H0i^t8i)^;BOAWJk9hiO+hi+M3HyfE8F+z3f--yq=X-B@b4D%qbo-D1e>) zTVFq#8&#q()<-wY=X<`(J>KUOK0DT6b`C+&HLvW^f(__C1naV;#q@uq-&w@VG>cxX zGb~#FCn?wB5z|}%;0PA_y&Px=f&Te30Hv?vUA<U#UvOUe3eme7felJB(tMkoJ#`>3zutM>!={&z5kry_8HFX;Q03eHpdO+&6= zo2pg4+_uxLufT3ZdOjIdzJJ|g1Ru(XnI@wZ{_~AW-%44?qF2p~6V*Ck%|7v4qs{{R z^9?Ns?JQ^+1Uuby{SL6OPnBogQEmNvqZzW52QD3Jqm!fW6NxW`oXGMg{pIHycCVbg z&^YM3z3hW81OQOkk&PF!#&T*p3iH|Ez1@r`=G z6$p0@0RqvNd1($|`PtZfX|wkey3HYuB3R0?A#s|i&i5v(G@EQ(TpFu&fBsJvuNqjN zm7F~PEH6JufaT)V<>j8(1LH(2ioU5OQY89#899H}Jy4z?j##|G<1{|S(_S)drYV)h zQQq%M%$m|se+F5S^V?z6dGi;T3Df*{n znORxu9UiWonm3SYHbKg5ZeM*9r3lJ{w#4SlCoJ>-AWj5HFRoJ7AQZRj>wW252sJNCcVz(o zlcGU4o_YUPs{jindxc<{+LiV zy52Nn#hd!~oaVLkyf15T{nr|zJX^}`irb1!JFmA84i%HGXt+eTt;>pe^p$du6-g2= zM}9q9%m_>yipafe3xwoZDP}?N7bMz&y55F8KBm_HT!wZX*}1XFAUiB5NPSzKtkFOo zk&)jTc+w?ruO=cqJzpG`vz$LRCiT(vW2yb^8A%V9&;oYP(lENBgiB#NMff4B{~zQs z0lEm}3G_X4weevpzKpD%G$zz=!(Vb6NySE#(l2+-SmX0NNR3^jqRP3p&PkQY)W18m zMZ{t==-AaFtDHa?%5E}rZNOZdf=j03cFg-1=<2NbGhvPur^b@oeFTRf;vd1YK+i!v)xu=hovlSJ8(N?uyrJz z3)RpDqlUpa*w}+lo>ZA5yF(Rn2F_o2%*8XfI*XL zMNvJh0>qS5AsFtjY$mP#9LlI^#gpu&7mj0|?}B$ItnPoWWtME3T*VcSr>+g;&RN!O zKdqPUm?@CNR>7c6y}}V(Ybs|wT{PE~elt5dCALJy@36^{sO_Lbu*&m|9mYG_LMA%D zC;k^XL%7XTH8c>3OG4GC+ZUo7;w++a+1uXFf}QMGf$a|xeBRpqVv&K`_BEJcb(Yu# zp-b6{k$Z}P`O4r0NtKA8^aSS*kiVnm=h&3ExrU5Pliig@Wr6DZ;r{}`9msUelR%=l9fgvR<3 z;S(WTFn5yrVpGz$Fn2Div=d1!0%Yd#K!wGjaRZ^FtQ`JMluY7q7U#hPe~h?~&bK^p z(rJk$Z>e4^cqmF%2a+W9D+p}6R?oI;6>nr!Ofn&ChO9K4J*BM_`6oN^T?CI;ZY`!r z7o9Dt@pOzDk~4e(Lg>O%6w_-Wl0~t4lOnF6vE7jHVxKbfNPJbR1;N3t9&Jg-lPl6` z>EQK9=uyumSuw>6kRQgqWzdQ=V%z(13CF7ohX)yt!o^u|rf;U%=Ce~&g{AoYGD0@% zy+c(0OTridw86tX$leHJo6ayupf>PRhA|%Ai{O|?He<0_&bXX8OU5p=!@T6!WIlsV z@V@v36Or*xm)`Q*1Qjqp)J7q(^|!#zaazzPD^;vIALMhK!9@Itq}iXyu5uTwq!NnU zqLtI18Zt-(GJTbcxamikro;j$NkwX@BxAE8@~KW+`))|iwUlQy)5TB@lrz> zl%eDbiB-n!z)O4u@2Dk;iTt#Djd{#=DGGJozzEu0)&LxG@Sjhsy6i0zS=eTHh0wQ1 z*wYzCM!(cHAhw`QP;0Q{2_6!=cqch6scpN6&YaAe!(k;Y=3kCxyWmtBx`2+xs6%j* zPjA1p12^MIXC28pwGk#J2V@Ms*XnNzbX%3z@}xjWuyGAT{UG&hf=q5)Ajvw?au>Fp z3WeVgtnF{W;e-vwJxg)~!!;WfJLu~V;+tK$2x`YWig!7h{?IQec2N#rI`BFiy>+c{ zATbxLw#C0=(p1Q|I>*neClgzf-$IL&3c!1zG}@3RxCSg+B7L!HVJmhNjuf=~R=4HQ z4Lxt01crAiHS^-$nIp4D=UWI=oW^^TIsH3y81B^321p+Ob*vqdq_)dh?IS&f<;vut zz^3O$idK(L#@WV9w}>IA?gqJfghqDsaron?oTjz<7P!eeQul*YH<@B+Qr_cq_6S|u zP|u5j2O*4F0*|grX3ZJzfsEIyAw{7aT63ZS5qhEpNazn6hybg+Z6L! zXtYsuGU}|@lvMMR2ifesH z5(aY8R>H`{zPh?H&7c#f z*0q2Gbs7&b2J({^Nmb)`D;yBG_dWLxmMP;Q;cA$wdG?TE!5(*^k7MzW=W=`Yvh$3B zy~SCL;^1e>S};}}LD88Q{#djFPtzMIq?4$@kd&E*)FY*2u&@P9ATGhV6cwk|d;}Yh z=zb#?&#L^7%uvm7HXA|8W+LK`OFSwgR-TpIEiLDP_c@u7$xEP3M4(CBLb4XRAt(Wa zP>}rAHXyE&CG-zNn8nfHnlZ6D8XVd)@|SI-!;LX&^K?+rL?t&Kp>$^g2T*b(YfHuv zH1hqtIPl)_4_MjIs%E;KQNSWI3)XmLK)>sDhYFzDe+#N@u)gF z>>$lu=)*QV%5!CrOpKly$cJw$qZJZW4Zxk?BUK1>IAqAT0r+ka8LfZy1z z*8`a_TyMWEf|Z(?d31dI1lIiQAf%Yq*4A_=sy#r7HDIaEa;_W>7B+hbJX}+KU~vi^ zW^wuLjZO&=sqK8Mgov0QTsd|B#{+-}+cA+A&xKoYPd$~n26JP5Ju4Qc&!eY8X1;Lh z&F=2*#ld32A_<{E939zw<-Ov9#c4e~YBvxmrYOAmjj46^b1gUv_fN#Tci(7X)d5e!d( zKdzYQB?FdXU5=>=yEhJKFaGlTQ?wDtV0KGU;GrHW&yOaHPFT` z6DaFHF%h(rwCOV&onKYwYJdwrxr`U=mFQ8r4N~w8S4jSpk}&~B03UxPs6R{V*GS}c zmU=gs#9f2-UF63BSNDDLfXEopvxxDMy-GCR`hvhiYI3h|(m7rVy%`is`Nr?|yCqb< zH@Ze@BB!=q>IMyNNvfKF>EKf~@RNPHa-S35-@iKVPhj~y^gM*6WpZ2(j+upJ*zPCQ zRc|VF9bKTC)D)F*&2_Xj z?vRkW!Q(qqnLGkiCyKvy~fUO--%*cwO~3QkQ9gtPO{sUY{0rHj&CQZ;<1&%V!Y&L2dET0_v_M)tKC zCGBGr!s_Q|AUytHC`Io5F4@!bVfIq5rLN+8l;pjUxbv9l#!q#F(dwU>m~=vpFFiGXfpe_KT=Ph7C_uW0>j(GvKOj(VYp9lc~3tF^RGMnkCYR$12Tv zHVC=R_oF*detB57(0K1W-&Q+3j}m5o`TOagK4;^cS>4>09dhn}+D2y+7{siEoQIeA9GJ5Z4<*mCD{#~{`E zLM6E>uPgI$%dMfyB%5@C$+4mSj8D$Oyd?jEo|%jRLcPB~Oa!`m;h60xY1D9J#ryOp zI!bqULu^|4&M>^hTh#7W5v?VAkShp|!xNZnaT00@A#iAq3inq~{#$V=Va3Rhs)3xj zc(%fttTBm%yLy8jsS{?3(?5GB2CFfw;A6(3zF~>c0z^EOk_%}|n-5~HV^VIM!2Fn_ z6`8VX7#8@ep#GQmLxVo-h3#+9oL9Er^9y{Qyx{ZH(yvW%dm>1nE+ER z`b&7hVGT=@DkKpMHZv2`;FO^7m3&n}%frKS#w&^Y`q@BM@Se>h0)La-HoZj7RqH6@ zS;E`k1@Z&xx;SxhpI%mt_|B%uW_2X1)%2w6ajsb2@cUS*6ku;}@9JpP13Zv$fSl#L zKgSJjt>2YZTra{`iY=}?VbiH~Pto%DkYIOjbwNiw&E;VsKacU+L_H^cP?TN#hoerJ z&X)vmA3_PMcjHKC@ltiZ7vnB0C}6T4CO$?zuI*|8=fhJePY2GC@;DLVE=fAnZmlaC z8s#v6iMkszjfz$VFtZUXF9NzNUXoxl>cq(a=qLnbm*<6~_xQN^1K#PbZ}b(v>?Tmn zGaQo+6k!W{E}Fdv-yecwyg8CEHH2A0-m#T8g9brY@`XD`T6A{H9REKsLbOd^eYBr$ zSo^0087TDIfrLh-&Z=n&x;o!N$6o_zEeM`^_SMY$r!EOFemRho;NPuymkV_}E|$jd zdJSA1;IxbMZ&dy}arG@HjXNzSoW?tY#bfUloXgI$mUao0ZaM_Ur1d0esfmo7w)!*Z#NYvwz_L zW&{6E3eC-5rWNGWt~V!BV|-4o`ePowxAH1J<3AF0om9!r9b$ih)FOKE#J_ZNj_k z%W-_QJgpS6Du4E;lFJ}V=Ab#ipR(+4=^Jyjq`kLkE(`ZzyyQzlqxMsOBOZlcqq`oH z(~qUFr-9B^*QKjPQJFo2R4_esd*8|vR>nym)6o?aSZsLzs}w%pkJ#ghF^8?Y;h!Qr zzpy>?@mXI)U8Yvj2kTv7_^>LX@`s(vj|oll8SQyQK}8b1JB0O;aZbf^`G->E;QxGh zWJI-f6Pf5@rDZ6G2FmX+cR9?yvXrF8UE^aP$B1g zKV+_|Z--eoWUc5Nmk=UJ+MJ6_jOjt92>jX)(U4vj?CBz}w(c_b@GLrf<7RRkWovx# z^nP!XaT3^~Gc&8JBLV_ zOPd*ub>hS0I_o8EcXYssnS&i;cl)}l5MbH_)ZwM))xwXei766>PZXQO#_o)dh)9S4 zDnfAA;pex!kZPzE6z#13*>o%h-)$mY!iOKOBxFgdGTE)gBCvW_z5hhecspTyuh+CN z;kJUy-J`F_h1L6$X&rT&XW`LhS|_F6ZDzLVCC}}wdZ7uit^?23jn^DDi>>$FkCdt) zF|y5v^$FO|5PNklhZ=wSANuPV!;_Q8YaMOL$*K*Ud9^tn(ixQ4ixg`0W^vWJ)PFiH zN)81tC6d;-`6lE7^Y^y3x;fsPpN+#=oZNEsO4zfo5C=N{fe_+-+UKX8srE+4H})6H zo+AwF=4}s>`L;uKKMQex=DXc+uhr;gWmN9~RU5-MfDTTiZt z8C!IPF0jR>t9WjIxL)kgpDYjsb}{}6LL427oN$Z@rKkkP(hyy5hOt)>=F%m0?AC9o z8pWmCoG6_Z#ajStDlXE0`_wYPVnUgcVk3{O?nUz?G-yF3XpS`v8-DjsaF_)r^X-AC z`&c#hefW*`c3{FVtI-e&+lP!_vlkn?ds|A8q2VNI%UU=6B7c9b zMELJc9Ts`FZ)PN07mJ>i}76o;rl4Yz|wp%|I9UR$DSlMe@8QlVSu$GO2l zQ3otU_c~0=BC*rYyIuzGxk$Q1EHLHs^Up!la3*C1wWiz7@}8++W&?H(KLE_Ye?;pN z4u_9j`^)N5gFG#)RqDeDlMvHau)nT?5ckKV?jll$>yRU_?^#vbx883ApdadTr33Hu zwnkrXnH(1NCk8PqTpAz&JFZ`SQz~~|n)oDpSj=XA*MR<{o%Pe(RfFbrAw)kzt(|C1Pwnb0-b*qJyyYZcNG@*tV%`Tf@im$}CY z$7tYb#VA&REN65cm@_mZy<>MnJ^b^lbt!2H4D?3z&I$|kiuOdN<+ z`dYP5%e0bmSQx8JhEXI1*Pc z-3R;AX!nYfVjL@(?_`O>t9Kj8q{wZUjae^PO#-)x~i`4q#Zx0_`E)_=^PN3<4mlrMH zbRVxC_rP&?|Mx9we2=vb=gOzco98wcnAiajvUa z0eW_mg7@ebu|9v(~_%B)>ft!J@`Stj|$t5E+FdqnD>uN1K#XVL@C|@-NzC zj^{V03U1wx6D7)F)oyhf9v{bSlGB2fTwVoh_PzHH3L1t#|#YzZFV;g*Z7+!#2LnyC(zm`++~m&&Uyg4KZk!aM!!bsl%! zq9_|@-5u_#WgW!_d73^R$3#x6Q%p5~ldN1Xj(8rrj2d zNez0gkfIOIhKD&G)(8@&UaHebwwO_72iGpDj$jea&;Fk&LN7gSeY~90T(e`C>rBF{rQSZqHJS-2{r4IF zVEIEMb_8+Yn>3OYXe@6yp!;iO3m@a-&FLEf@K?XcD_I;xb{|_8rYT&Ltn1q8nB1Gd znk(Am=XxJ&}cjgldl64u^l%n%p{71-3Siq?jOI$q| z3IE*$1w+2lUh$l_{UPEZ`_OW^3eR%EK=CtfOB;k#;)Q0CXKN;>z`P~(GUL6I^V!N! zY^ozqdsVr1dk+)iq)UNh@YKySr-Rkgd+CJ!J*tC4-j!W_q_}L!-O|yc$d&oDxgd^t zF{;+JchGD*aiJb7nAX)iOlUgjm9@WrzO)9t}?O;af{dGec1 ze0HlJQw0(Ij$l-GNy&SC;g!z64Rd0h4Ru0|p-^rFz2{A{X2IA+kuBVGmu$CFc~cfW zNL%`*Q<8osD((lgO0>a;T1V8ZP=jX!)RV-Jd^b~BlB<7Gs4(2?M_V<4B{=NXK}TUr zM8c!1!{vcczr=|q!Vr!V#V%KHhM!~cAQ*~2wsB!WfaaQQL^qUHv9L@R-}PSIf!eu_ zR);Jq+|6%mB6*o+)T}=fvrQ@Q@3^M`1L4_{ee(O}e&sxw;X5|0e8FqT1;T$OTR(p9 z1&>QG?U?m;@aVPt=@A1jpqjy=Ys2Fu8upv8dZSw^K)19Q8;FNz^ak!buh7DXRN(oSATTXr-)z zvdn1e`3*)OAu7zPlQ0%+)yK~l2dZ4b6E_`qk|kkwjG2UPWa=Kmo{`<4OYo?EZh2m&9H4Nowij zF6g&dmG)d3*@OW)xBDIh6v0*Ws_);cM1a#P{o*eK1E#jUy}iR(_tOpw zP4?TWip+P-%K6@8qE-2P_~2R=quj6y;gcEe!`=M{E=H{>A6C4-&%O;kDESP2Vi%sP zd-z41!ZA8?U1ZCm@`jpQE7#)B`Ni}TzvTC>{ze<+ZM$RpV7no*m&>QQtGz%(W}XMK zHX*=qb}3h91_>+pJNkUiLi3a*G(|xOdRWcRxb>&D-^(a^zj3ou9~#=tce72oJSVK1 zTR+|Uv=r)Yg=rk=W<>EBt#aD1qvQMV&eGraI9!D zUG4o%fBa2Lmdqk+mQQ{vMI@SEpIwq^s6(Q))-Fr3TujZq z9*Dr8lBA(F(!7DyOD^2F|9LK{GxDi(-4FYi`{wMS1;3LwAvN`GAl7S}OUB*I?&xOf z;Wu$2{7Ww<0P*G?r48zS4MwmFV&Y?F*)^0N7K~O4loaH3Mnfs&^+K4|3Iw<-fz1B` zv9QDIl->nSP3oy3LOn9Nb4?ZujzV#r2t9!7qFBnuw4)~yp-JY z-GX1c4yhk=ss?O*J-Qt4FPoxQfmdcrO@o0z=FW#Zp&yY>m-rh;$ENVoU`0lb5KQSy zN6b+X^xk=?dkVwpk?xIwavXh-aY@WJxr>LTJaPe3( zAbU)3wn&mg4Hhg)$y#~Dl{x9901*X@dG(OjauYWcJ_MOfG!Ns)ms10A3GTgqXlIPr zE9bX`;7}kAsAE>O<)$HM!~5IU4>WdkTb+3Bx`XoWCT$;n&L9&T$?n^`D1=O-FA=3K z+;k^spNqU=Xw5mjPZR-1DlBF|(MuOdFSg-sqUIRz!P7!?n!4?AT&cr4&GBp|5|h%# zXFp}h)mQvsibTS0bS3Y@#PF}{+qgE~p^vfj9ToStUli}T#K9!1{cKeHKX#(diA_G0 ztgi(i=5;&`6Ml-VEl32H=y@p22JZKP$34^nGBSt=isJV-8vv*-*v^O*aHb-wvr z4)h9a#^U^ITQpxeI_&J`Z5M#zNJ^O~UcsScU&L`B7@0SPfb^`oDj%_~KOk|Ii zWu?t`9(mDMPA=aj#3FGZ{jqPOjA7f~zb`dpZER0wqTk6Lf2rPCY0|7kChqvP*VfX~ zg6~S`V=a9;7)A8(>)`M_diC`3l!t=3Sywz8X{kU=p=)(*?C9g3+1`iRr4APkc{}=a)eeDUjA*kF0Ht*A;TT+uGx1Hyg;nbw}dg43LFPcL+8uj_CK84p@`> zBE7f^&K98hRdC*T_%Uxffq%_P3OJfjeM-OdaPf%vxvYB3LnZ|Aw=!o3LGb(Q*@vXE z&Tl3s_+77fnj^Uv=-|MC?T)uv>E3aj#r@`R+PgMs1sWW~7yXH^;9d(o3p*6P02i_s zN~vD+M~^1#*1+l?3)w-7&pts{yxs?v1O~e5dB20kz$~x0 zIM#F;s_^(UwY64dutUIkbXg9gf(usMHM|+I)Fy31i zt-K9;3z)_KTU5*=ez4h~#W$s2WV1Y-DC>>amSwmNiDqso!!a1d?7FUeq>GyUDB z*(-v%uQ{^d_`Pb%Ao(D-RV@{z+a~b22h?ma^x&vGckUJa#>VsP2&O2j?csO-j+dcA zQ&i|(t7J!c2wBA1fjS3uxn3VBiL<=?#JBPlsJle(2|#+#bC253o>^9>w!OUwEKJAH z^9)kh?5iRNb0-slwWa;Bu?6+JwlU7L8RpF#W1@F-JU+|IgF25qdtaG0Lptwj9kSHY zrGGVD90b5I5>`(x*`}uIdwUaOf^44HvueximnLpy53_%@7g^~xXzoXZf}1BE(b_+* zHrMdBeXj88R3(^>%!jN>Xxf^~~%!gpKUZ&;j3IyOUcZkbkIv>C0czoGg zo8KC94coteVH2?6#Lw>N$&L$gkLVar<9s61d^`*^KU@)^q_BeU8 zJ8}t@Kd)ui(eDp*rX#)L7>(W|tsFJyI>*da^kO z&RTIOQOd|tTNbaU>Zf_u-(hGd{l5I=6uv1+@#>5(r1%n!*c!7I;;#uaaV=n~m;QXA zVbw}qooojg6#fnmj@#wp^5hs^BG;VvueCx-QD{P(DFbWXCqYG_6$osu=ZtNcX{Q(8$y|O(WlOTrZfI6ePu>9 z;`kw{Sa);NlqUA!wn=CBaNUyfvF+x4CgiKxSkO~CqTB6>^7=ry|pr@rl?TaH?ci8sZrcOp3_d(1y zs4@7s*QFm!R!Y||Gg1DvXIzZ@r^iXCMi4h-l8dE-zEO8{n`hV0?;dA8*(>^;l)*Q~ z$d=9B!#(7k6W8Nz*Ray!ZFNCt$!bpZ3}QI@7ud z943^U_RvsE?0d_IdBL)FqFWgPylBn4;jBS5yrtr+1r2e?k@%C5b;`-7t8W;9QgpDW zhVYU8wZp{IZ`;esoFlGrjce|o<9?cpJ={uC4rk-@ zp1)T(bv>Vyw=u0sci>$dLj+nXFw_0KP+@YiIj0?HxH#OxUc?FM#T_IG%X^>8Gm%%D zXDgry9kQx(`~4XG5=mTm`rURUx$CR>`1=|=*roO6C0LD=pZ3Zc4Q>n|Ube}n#ucR7 zSn};?m2a$4IL~awwZ`@9UdnCv@o+mFEeI~30LUb)w<@*kv|p~>LJ`QciS7>2-b1L}83wq3$fJ?u&$ZG>koZXq?)%M|F%(zTBz7j%*l4U|nc9Hv(;?`M1d z^yTi(UU#k2-QwPH1&U_^$4$kIUC4mY z+r{LigNrDKiiss-k#GD5l4}Lo&$ml!&t*$Jgs}r?v(y9|SD*o8_y)r+`f(~(&Ns{1 z`^T2|OrT$cr~|ikyHCG7vGs5SM_DXXlJI)noj1~P?+TUQU+__Ep`keRyCnVSkej|Z zT!KL(!@a73xLQHcy!&Su;~8Y$Mb(iI#LdOB^|P>>r3EGtV`Zd9!W*aA zTeNme*Y$xE4#Lkja5WS_!NldPc_$?@W!If_5TI6ubpvtN-z>~zL-e!o`JMS7sMj9V z3!uyyRZK$WXJ47B_pA)Y>0)?(nQAzn*;P9+Q<~%T7(%bc1D}m%=|q>F9@VYJSt|;0 zUB2=<**-GUDh-(Awk3mAux0nkLjg3niWXFQymEF$?I*j3%mXxVfpSS|De9TEuoKUw z!2>*nCmlc!Ps0?~L^FR~%x6xFk8{1H>v{561uH4+vfpyyS}njIb>PP8&6Tj5HY1op z&?R$YBbMe|PSh*j7^VS}Pw9dLGGx&epTti4aRspo+QosN5x!!(R*lpPC zHO(pU!NQM#xyy>zH$D0bcfWhMJ(xk0rA=441F48&RGF=mEM6v^OtW%q5SzUZ)f(H) zxgw{icP>(GbJs1#nf9V& zLUAeg@epSmwQNxZV!k`ZRfO~iEf$-`P4z(#71wB*8?8`^m$Vd>$)-rBUuWslQMRB( z?0MxJfEMtO^~{W#CpJ2|06-VH*2Q@qlTM zl}ObVgEi9gfor5mW0(0J2S;S0M$xUg)UNyFJ0ALG3r&gVN^|%d5@2uLNIfl~FKh+I^w^f#TbRg1hFtdgnUgT*}ezM*~ZAO^RqUl(7;l(C891mF? zjyI%F5lPv);e%G*mDGJge1s(udB6h1u$#oHZM^Z&-QSgGorcaKidAM;3FUsior&U5Yq zh|gmM4%K~~1RJn!Z73JiB?zp2=mG{f30>!*GQwZl49zAuEhG*Cjc3#K&Tq%9DCqK? z|J;ZEU+=IcN|Z%jzASTCSKyf$q!F%MO@V|`B~MgO8>*gI@UdLo$DN+Aq^zm)yx7@T zKFp(|6S`k9QdD%STW8V9%a&(lZkx~7CIxL5&p|N0N8EgBna51=zKmZM7Z!Mv0dOIu z_vT)T7y%_g>21>S@?1q{|M$Ah>+rPwWC+80rLE#KxN326xB-6m&0L{%n#Hw7Hy~+T zIlTAUvwZYeUr7jPan1CbcR{r;Hm?v}ppc-?)(?oKJz!tDT1$6VB+wwM#Bjz>o13$- zT=5w7U&!C79;h=5KO6wIV6t^>v0ACFgKh$U_IV#xHLA9=Wr($byr=w--!g(^<$#7n^I)nPN zh9o#}yq^Q0cWfYqh-~H5 z+sQpSIYrS@`9v(4Dm)=v2=Ay9yl59Bts!}ci&QNNb1#qck;$s;fUGZQ2tLM`!Q+A# z6rid;y%G=3*d?FZxm@?vkSI-Sp&D$)9nzuw&+xQ%m%tq$vX9_IrmlH|x`SUaM~xU)nPL z{SziuuvZGpqGk)u`KbDa;;JQ^tr$yBkD>Eq_s0ug8kV%0!pWl1^wH{wp8dAZ|1Rg{ zWq?aM5w_z;_2%*bs(zuNtEG@?d?8D=X{ZM8lNl7ycDp75W9Tbln1C$D!3$1XHqJ*c zBRwd*y9WfUH7kWz8`Jl%oqK{C)f#tpH&&PC;o@Zw#UD=V-YfXOr|K7i*Ly!nDm(#2 z@$m1oe7UuT6doow<>vJVal%TdgY9DUTF(Sb znVh~s;J6VObW6N!L{wa1KR=;_;R!~4czOM1ambSbL-HLQ6iTT)%pH!#LAYyj3n)I+ z*|x-?)uF=KYW%J|@CR}bVf|rMX~dRY$uaJGV;M+n5GK!h7lVv)9(%a>zYm>YM+ezw zVF%r{3&eGw#Eg^}Jvdxb^x!X~EL%+?_ki!<(ILI#_a4*yD$tChYR!)ZzZ166FPM<@ z*I<8%E1LPK&c($PUDR=!mBZeu2W#J^8t3eL*9A7fU=QTw-)F+yszPKQF&r}7+^eL( z_y!J_$)3$+%B!viqAgtmUf<-1zxSY^c~k~?C>{xywV$Jp9wZHbIYZ@|GKZ1Gb&pi< zGl)Y)z*-|mrkSSWV)yeF_61s?b{xl1GLIRV1R8xaT(>TQk9GL;P3Y{+aDC7Pm%+SAdbXifmFYI*{n_;@H$a~(Og)~5i*T7TuxU|Kd{I5M{Y{8lW%%__5D@`@8(6tlF%@skalh{({p_;m!+_{(I9UKwRdzH@vv|q1y1ov~ zMFG=DJ&U#}0(qCdHcE{^`QRQ0=jAOyd+E&yMZZ>9Yq=xktJvccYP`NAyiWJtP5VT+ zgf8OtnipA{42YD-mZ-LbjDwTEs#1@g>dFvP;7q>Q1C3z0VVA?H!fZD5#*t&|3wPPU z26DHJ>W{K}?S;aq$nebY2$S+dscwwU7BY&8=VxcMEZtJFvbdOd_@U1^-Ugl?egC6B zVY{%Uje{*)>wdC9v6Ej0a3ZCB6^!q0E|+NEp;I&zqM0%mk!`^xt1Bs2Az>EJquPXk_!!9~f?vSAMZ!)ikk-Lf{ISRtVvC=6EpMti>gwb9j zy)W-L>lt4LJe)5|M(QKLD3k=v%qQwK-t$2rEe6<|@t@PLWm=*;L$J)nhpSr~z)`_3 zd{F<~)Pnp1>7^F-Ux_PXYRo=UjTlkgo__5;izP3Zya9&aPVVE0UTVn@o3Al16uJyu zcU2<0J|^)w(1r*)ZYTjf)rlgFRv9C9=6Lnc5F2@As&*RO9mlr*?}lR*EGVm;&(RA# z(DfOiH@+Xsj=hLI**)%u%!R^)ws>#9-6sx>qjT`%H!aZ5Xc?2?5T`FC$_j!QFwHL$ z;s=nTYW)oHLJXy*UtC6$0rYzu#+k{qOZ{~qUVJ81%6M=<*<$VCG{-O9yRa$zhX=s5 zC$kZB+3)Eq8gpe5w^E|b8@%{UyFsDLS|HZ@ zpe@eMD+ZD)tobfMMbtQV@>Q;a)7@14PC(P&v+Orz+1W~kFi(5_y6eG7Nq9L$ZP6iI=mr`Jhd7d0ol22PaucL< zS}Psq~SIJ|}vjT(&N?W}ZoX%UX}m&35Ic<(seW=+Ny@X3SDeIzz> z8c$gho|z$H9jCr%$rVya>%*XHR=$xT!3AIRz+(GjnjnOl;cT|$mPqjCd9pK>MVu3K zzTBWVY<7NYsq&%)$pugMVqf`lEHZJr1%mpGf{57Bi-3<-A?pZTFDB@NpH_>$3V#X+ z^GMl7mFKWbDkx``Au>MheS(kfL3wpmxjSy$Ni!V(DKh=qS~zCHXwfU^BxlfIPMDvX z8bR&9cA!R!rZ`*u#TPR(`2x=&h@lu5swvOhg9{-BNMrJFg&&HC?3EN!%F6&RJojT= zg~l5#fu5PMJHdXtU)X6F<(i}xIu@DHG)2{drYpsGW1JR|iGv-Yh)jMNMZxlgbh^yI zkJncnu1BIs^WEnfjH5H92c4ld-mb}m7nK~%y$es`$m?u-An1mhu)|WpdAGzRa^#+& zi`vq?o*W^72=$Ny6f_qE#bjKRAlHH{Ygak+dP^$kK7F7qN#~GqhF>NR^)c-y(Xz<$4 zQ{?ZjtNOj5r=TVmr3!H@WYf8I=z58XX}Y;`<fho&~1r?)Y-u-!rLafIE-)4YSa1RuVP()VYwedI|I?Nxz z!SCe$BeSDc4ck7oi+@|cccuYjTbTa|FM#a2UppY;tq`6mWa*FB+&&0leW)sIzn8D3 zG))=}f4P+$K)?Rxsc(}1h?~4y`9|e^xc@c&@}Yy?@@u$yhB^+cT-v`xq?r6jJvazL z>vls&-9_f+R?g?-&*n##el<2FA~s@$hB90&m1lBves*k*{rhJiDD3(-Y1ygx1(_LB;5~TdXdF96_0jn!}D9muaf_74hU7yy80l8n7 zlyGbtqSu}l_rJ3;h3vAjG8jl`i_2oTmyNdz=g!XMJ`|iJWKpo~q*H83%zb`zWU;Nh z+^NeFEL?ZL63yTd5I~+hez#LocB6N9oyw9gR#}YxkS{a-ocv+WdXi9?aikZecMJS3 z%~xIfH_TwbQm+vIy;Mp{A2Ml`uySzu+91HT0gk%rOAZzM zoq(SgwKjadzY{hxIr+boz62tHS0XS|tsi4@NWRg~^4Hpv$JCz4diTC@JLzWrJ`(Qi z)Ee)^TNk6T;Zw=sv>8NU7;M)jnMehiaC5R+ z7@O%SbQno;8>pHS;xvP`+R@RyB?Mh7&?BgjPLKOP)LpSARsdeo={LCghy&5q@{b`q zopNcZ$baE`o8iL!t~_wN>LVsf#PgcLp=q9NAW8SyI=UY?SB31VT;Zb z{Sxep2=rc1JV9!eI;pJk{M$*idl$jX+=%~@K zj%728{{78Sp`eSRzLiJl0^ef($3ig<7KUVMGBUw!#gyk(lVt`uM^)X-J|n}!lh)>> zLq~}4+Eu(p2L5o)l+->j=Z!a-Jj}yl2p4ty7+J{^9;G_PPmmQ(Uh zJf~J=H>&&l9#yWwKd;vXJgZMY?<2}JfbX=^gYTsi=10o)#Sh8cvKY-T2lN&ovO#Vg z!B0s0(zQ9r%sHA-qq8S z)t^ye6p4uZWFfD4IDU#035Y@zKBuL*VQd@&Ha|;8J{HYwt1rQsvK@D;PdTK#@{<}*>MK2I)Rdy z`y~flHS(X-rJIk-E?jmeyxfCPJM0JNOnmfAiGFQDddI9-`M%^P{xYf^YLq6PtiE|o z0E2l@gTGvt16>>iL!wO~ev3k=KQKQTvbRseQA(pG;8^W_TW@Ull(CVtBjv^oNe_r#2B4%OKu47);Cl`pweP5vWSc z%ui6)F2_?Xg)|2KuNBYKR*1XL4)eca<`W#mUYh4FS`E-i%1*c`DV@l_JDot9xPijN ziB2(!Mv8+bmLn&^F^`B0%2;2Uf8eb;au1F96MDoyr?w{& ze;+*f^Ewq0+5t=c`h(lt)OytkJeylpx;@?SW#IIFVuPqOjm5HIsGvP1dpmE=N~BDF zI}hsF{q$@Muw7XYhs9(4P&ZLQ^aD_po8->4{*6dBKTCN2{ZUZ;lkO*f+&|!4a6GhowX#Gz@fip!EzZ^O_d@{F zNxkJPO#MJNr^+O{ME4*MIt#xe9IK1ceMY zZW#HVsEKsKqkJkg7Yje7 zS4ZtQD&laKz359CJ~pz$4%CNB)2LM%K@ctzG~t2ZNIZ#Ors@B+&W8?XcBuZZuOh%` zM*$GHk(dg$fSo;BQrBK)t#^hvo$$PSXd-9i) zb$LRfInPt3u)L-_p}oF(>OG!>D*tA?{*bnU^jUe*(DGd4V zt56jLC!VUjU`yHU<{*0WcaT5}1!;Ad11^X0B}WW_<|{eckq8R$6%ME!_1+H>P4sT% zruDxP&DE5oHZnrk8lue3$5&Wo7v_zHQ1Pw?(O~MuI^ek%R-HO25|J;NFzK?7GpV@s zrNK01Qg=-s4ri&_35FTUo_(5NvPQHKvGg;fSW>k!Tg%{#l2XzHiWV_A0skH(B!3Bs zV_kO&zWk|se5{D=Z60%MA`@%P(!G4LRFOKCixA>1=xE&`6rAieSnl=OPcwBodzuuL z6jt5StKx-7;1~Y7g638yVnGqCQ8StbPHR9m#`yCpeB1WPF$S$s+iRGQ`nOrzt)TI~aO zQa5Uc>0FmrVh8P?*yN7zEr7>@mSR7#&@6pCLb#-)ESV7hQ?ZeSCnZfz#tTHpV)fbJ zND=}*=r=IRTeh`~LG}Kgbi~V{4mrcms@MBqP@lLcgdRVu~hI=yT)mRCS4DgEi0 zFKxne8T9LGS_kz@a{ejSiFEDKwOGO1w!0$fSF!g&(R5GQD6_3DlZhDE)fzt6ydIt` z(p9Z_6YN3sC!JY-ryRZgSpRT-`gP*(_Kbl44vWaZ=IBnL!URiA#-Sou%UD=nfqYc( zHZ88_>XCIrg~S|HhQE;*dZ&L{a+Lj2Eh`lB;JQF7m8U+Lc`dNCNrM@0F|uY<4v2DO zRdGJwbGrYAXH)so*>(_}fP;q?^mg@=>Jl_K-DpWkw?u{JQ{4QoRrKH_xVZbV*x9Gf zNE5X(|J_{eH*g_&R7zyOf%E=Mm-!~Al2eQK)o=Bqk&9bBmBo@IIg5#>E4|)~@Y{J| z2z4+97cDsaAx>_inh4(s)LufS()WF} ztRD3~$q{3AZ#PsIhf^jv^L+KQs_-H_Pi|XAX7Fiq@Nkk49?Sivd=)UN@_0nyPf$@=h;wS{Kh^ycVK>7(%tUM{y2$^0)|AHsr)* z;k-Aa@e5TO5Kd|V`WP6%CjfyF0B%524lSJCP>Ch;0g^V1F%Y`cIv;(4U=4o{QE_0m;qSHOa z)WtrS!E*b0Ds&GlH+!6;qoe&(nz{b2J4uZM9E8cS#4P<1f8sx#r$>HxWb9vNA|t(h zsSL5GR8Vlgt2Nh#qobuozu{+{{w`M0RRZvuYDFozkk=nsqL*}gSEeN+iYx5(Vk2dF{{F3kn@6p)sZ14#P z2(+70^ptE9xn&r$4MT!^vWGReSc4!=1}yLjH0vkrEFKEwuRA)bE9_<7euxzVZ6`y_ z0q1WpzihV}g5-nkb-{6ci=T)$OkhPWY{>!3^)VHiL3&WB`CDMR-*6=mCGp1xDn`bm za`vob9-9tlBO@dKlvs3gCB7XcQBhH^J7Wmge@VRaM_9JJyWz<%?2+L#_ubX_IWLDj z!t}fSi7FA`SwM0D7aN%O!eGI2?WO6lIx<(j?JyMY=vbP#ehH`W!3I9c6!K0z7kXX2 z)CL1TE5w2s{b!5?ANScE0^iCCwGv`2*F~fL;@{f5nz2n|Rq!nt4h|Tzd>}S5f5C!= zGiY(i85mGW3O0-Q;o;&^trTfL>SFp-pxqBltShAQU-l%C-=7}_2vpyD*m`#e9M3($ zKnn~H&3I}Q6kMfYMm2;@OlVWl@ix0sw)SuwX0ttZKSyycB17pmJ>sy|05Ka12#NQ+ zJsuFbyFFiP^f)II@!DzeN2dnMTF`$6AlQB&h;;R~{mi*}XYqvW^IFYCJHqRD0K}BF zdglGpj%vdlRf})fuMcN(8&{IOkDp!xy}0Pf=GW#Q|SWh{bz zat;%fuPlsQH09v>814e;N>K_v2G#PCtxC(jg?8Iq;- z`OH{g|0sD`DZb#$4;HkN3e{mXc`O8?1l%6NkTc#Aia$6KUer4b#V*B*$!h5~y(4B3 zpnYF3iNHi~36_XIlAQ7zw2geu>QxRmC}EHLuwJ%}sDG{Z@-m*xbKx!`;x$|8eW7xm zLQunp=79m5*U%%|?j%tO#@bWjI zo*Nlh1Wngh9u5M+H|pDV71q!A*RuTg`%BgtKjc$3#fPv|+ zC>ek4fam{n2L$}Teg&uUS3%F}w%xIah{&Fwdf%UWPiQ-ULW#Ia8_)QM>wsWoJ>DSU z_XPt6v79K2bSa*$vK;$9m0vpmYP`Oyb*oT%lVf{%w5nX&3B9*#H%<3GIiSLt6Y;dQ z->&H=RaRaynx^L8n8#!Y3Od`M4(PKE+8U&FYOj_hU`@E6+>ivKG<5(K4Lb#&Fe^iU z^SkSPr@fiT zY#aNOP_g$#?IxJF7q>2-$7`NF05@&Y|NknBJK>T$y6na~v-RgmT$Tt32=qVRh7vK? zYYP5^#)#-C?tUwz{paQP zx&}p0+`El|MlY_WZ)gv=cJ8;h%mx`>mPuEb2se5Cq%g=m2s8`nBO|t~m=eA{QKo=8 z(9_pwN2Gh-&D0hHua;h#riXc0?FhMGNtu(*7Z@e4e3Z115!+s=aRx<285S3ymPcmz z7B3sh!FvrYW)M`=I*GM(qR*$ZgSQaRF+!hU1%-ziYyk>k0e#ce9;Ldob?3zH;f?dR zECF&Gf_JqZUxaoDpH|uqb%u+u-}SVoDFuq(&NeOwdCe*dL;3 zP_({g3@1y$$Umec;re@ewx=rSlv^AFC4PL>u7B@2Tjx|!Q30rD{g!_uQ^dC;?Vo;4 z9Q&lcbmPYzeL)jhH23R$cI_KO_;2X-s9&*nodMGDf5;rplp%y{XrP+d#07&f}16{TE9O+%}dWevUj za(8>hw}i7~M0&^$arKZZ8p9#O5#07)_g}01>i`1!W%lt5A4V;ee>g#_wLj3!;IE{ zHtCE$SZ`nOzL*E}E;8E*mIHyjO_D|i-eHPbBE4%820ReQ{#$BrjCiwN_ zp_TSMa2zn2^mhNbPF`IPCsnC3s1PmxxHT#Us%@KQGLI$6dKs7H7APppZ1Njs`$Vzw zh()NC64W`HugJh}n&PJ~d@;h!t!CrlRbhBIImgF?*A;xc{RZ-Z)yje4AsIV%cI?uS zU%Z6{MxFhsb(l|TO+Q_>m-^9p;_#a*91OCbrGek`Lu94DN{3aqO=lH2qs1`I;0Z2e zT>Hm8SQQE8fMdwgoyJeQ+Lh){z+`autfB(QhxLe#iF`?p5FXn2!aXg@BI$_@YrWa&3L=Lin?WLJX$Rta%k%4DTHvknvAm8Fe!jK(=XA2ZdKR?evq)aqil|;Kr|mh}ZX%&qAQ-Y@lkhao%Z?xte!i%gS2o-K02r zhem!bs#fvsiGt?@I4ko%KG?oqD6G&UyBsX5M?ML970@PNC4Ct7nb)I%inia{VG`6; z&R)*`j>mClba%~*Wxu9+HGvS^(L{PP3R^r^4BU5?Jfw>zIqW(tCOvx=FuPQywF}ue zC|f}JGRsui`_KoPOp6BeR&zyak5urQsSVp*eA=O>#zA9C8oGe;De(uViHY5O1uQY| zCZW>v_&nE|<&bD05fQg*Q@T3uB!}&Ld+W=Ui@uOBm)ncUP;%1kBLnAktT1Qogt5ij zBIkWF^W(k9n9BgI0g;(I7_JZ|{wc5a*5JOPpsmEzDmnzeW7i;#TZ$!Vzh*D?5cXnZ?n}#TKF*iT&>Xg1ZtFoGXX4QUd06PySST7+sl)AQ z?!tm9i)PX%aTB5Yo3f1aCp8snF?_s328v-JvW{)EZPUxU?07ha%bA8nJP;dLp5^(5 z7vfE)f)&@58#HpDx?y!sLC0e*A!2xZj$gP*{E%u;HKf%dv|=hh?k6G&0f9=l;x}0! zlm2;@@>xCP2Z&X)mNf@7T(Z-#^QRYB1Gi>lzlNs4l7^qj;$xny_U1t9+hbJysGlui z0>_HZcd2@@I&dymdn)HAJvhdeFHdgjlV&7JU!7I5T;)ZoetQI`BJjVYL=Tby`D7V{ zNdkOn1YTNx#{5|F0;MY+N=|BK`oU@q<)}7l@aFs%r|k&15~ta!+z5T>nsB))Ov|R> zZm`T_pKOqak&=4o-3`G;;iEF4e#7$F!IIWu9S`Kw#)-NUHR&(9^gx z&<+TP5*HWbtW`Pto@B|;YV@nX{)W?H1Jjk~?>omuO&Rwf0(-b?_^IusH#=8E#r?)5 z5KdIW$)B>$IvD(_t~GwzoC+0i($W}X^WRH8B#g60QtM9|SXScpSoWTOSTA9p1MgXm zBmpM8hN-Cg^R$0k~Y}5MJe`Z z3qA2{>#M7&!eD+Lp7WKiXu6WuFg)=?$C%;A1t0Pw#8TS+(6$+OYWRB2bxAZceM8X~ zSPLC&U}`UhD)IJswK>_UH@cRGUEO@(SOwx033qpODyOu#tjymzTKPuQB-!8D-vyvJ zE6&!|%CuZxyQKC*sX38USpQcaQYdUaJ#p^4gu`$;b_T@>TaW=xd0zu@WRr3DTa>FR ztGOC>(qizf6E%2y?7Q?mH)ymBw7n8<+Vt{dPZaU$EC>@TPkRS{5Y^ckWim}aJS+s& z(!(fVW6$L?W;)JkcTz{G&6(DL+Gve`fev{MU%91)m6eKE(bZY9Y&3L(D_)5R9y>&}u$a1)L z!})A-Vsy5#FJ>xS%Z7z12f}4WYTck;nf+cz(n3f49msJ#l!r zI+1FezyPiMjYQ>cdEl?vF(>=- zI`wktNzUir?p)3`jWMx8UD~MIrj|*#C5x)4CviaUimAl;>86oqhAMV3W+|s<$tG7z znzfJv=3OQ8bitZ=v@}yJsO{%oSyGoB?bGxgU%9@^%PeJxqxg5!;yhdPE5V--b^{koz3IDde%frja#WB>>VgNdC?aykV658+Mwf4YbvG}MBca)~_!Byg zDi0qU@;^%N`r$?$6(gFQmU6=rIHcJ4=4hBFRPZxJ1w7JISEm+QEGF-vEZ}4m6v%h2 z`nPXfrBbIS2))S)yi!YeoZU^PNBoe_>MLwYr5w}`^TG{5nDt!a)5r%i3Y?*J_h)(JP>-Y5+dM(n330^w*-^eZ6CU6pb}h%EJ{(ZBM5IaXP7FKyPp^g7 zxr4lFVz4@WkLDMEl**4EKb(z?=bhA_N>!6G*SOit^?4jwPkJRGP3JxfHbvbN)UM&$ zezwXVN9Cyk5ko8B1eo7^c5~2{uxo(uc$ZU4gix~`o@X}#KP=+~HLeLbsJKw^J;KTBzELE%=4~fg@+ca5%!X^=~DSSk$ zR=Wo71Z`L7QHOeqvdBqz-Ofcir;7v@F4$)eTXm()Dy;8)il_ETd287Ud4*Op8}QYo za_oQ}2GH(h;vvO9n@!*62;N6zLR(1o=FiITVmJ|aUs=53%v&(Ot|%2QgYXE@JP~b?Z@b7KG$}C%%@EMSEn=X|&&bP%iUxnb-~)WpQ$X!_S&zxg&Zt=<0UT8vrHK?3=e}y zNJ_r_oQ(L1Qp-O9LCgzQ}BkEEa?DU*9$Y%Mv!|ae6Te zWAEextM&(a^;sMN=o=k^@?>~1alZlC`)KeQdRujV5ScYaE%Q7e>|-&XZ4A=%U3k{i z{f@w^y=alfJ&iYROu)U$P7-=7?+M*+xs_Fvess`h*+0_+RiZRp_2oPuG!4#Oms(zh zM^I1h(;!sPiMTDg{pBHHnf(3B*G;h>nVA*wXkOh#{p3lX;Lg(}qYP`SZfO_D?ryaMs4GsbW$e(!)=(wEifn^;XZLcxA zNMY3TF)@>@KZp2fC@806o|;+`6I!xdUf61P6)z{`OHKdk1vr2?;>%;IOKLpz2y*rA zv)BF;x3Udn>ez7*R#4a8gjes4ZEJ?jeYfFaYiI!dmnD{waSsf=J*RoY(v0*TBwIf8 zf{$|=Ns$o!`Z8sf3-;xuGUL1&xV^LEDHs-gzFt)Q{u+UhyBrZ4YH1p?yMnX$GX!-% zp8H-9zlYxTITo1^lY7@?PD&=!o#1vbRn;M4_1L=en{_EBUs$tP7>%n43^v{0{y&ZW~JTx7p^%dnfL|2eO zs9P*OinygMrzO)ruay50d=auTl=-VJ%av_&hD(T-RW6AaG=ttHI-)4wtRBFG57l+GeaZmqINj=aleDet zt)uSa-D4u3#AERh4K3eowhAlU^C0pa*6dWCT6MDZ6Lid%k|=Kzvsw*tTJxIrISlq1 z!v*av>mdO3rRr{Ad#WJZWpk3Bv*yF=LRAxGvqSXiOiCP*v%s>5{Kv|NA*D-3%27Cm zAOg$EDQu&@-bp&n{s)%WWSVjTBmB-2K~*?hU_eBz0wp7eE0GWd#NSC%NNx4B(O#gm zY~!buIRe^oDS@70s^_*1P4_eHQ1#pQ7y8@gi_Psu65eO@S51jx7gH*ZE6NlasIuoN z$8KDHgzs6nUOp=8{QYFaf2xaTB6mYRaC(L9B>3hQzFHRP3zaRx% zTtm!eBAhLCmnG_|&0F1Bl6f%Dtv(!IS<(uz-({fe*WW_4_$w7LP_6OFaGw$zpeyX8 z{4kUpEowIy8sBbgXs{f8Y&PV$r5s)*8w9m{J^%B327C_|M}D#~vfnSzi=ENv?gY)6 z0G-cp_;4?gKsGjP(aOu?Zm;c7o;{}ba_9L&=4pR9)Dy*`UIsia=|gGxn>o`Fg;0Xi zyfnNuUYwlDG80Pe+eg{{95qcX>t=YV3n9VP)Vza(d=wd|J=y$;LG}Y;r3+oeV zXXwMJeT0ei^OL78_{bJMgLq1{|GcUlVP3rXvFWo!r1%C$pt(^JoZ%u-L|IqYEPf|S zadB`mC+5K+D*BUrw?3{#QBe$wvFzCh)ncOY;^VgEW%3#uv;DN{YFTqqq7@qm60Eh@ z_YkBUcWdmm7;>glcg17Aqf=pG#x!qkzHC^G9i}b|iy0*raN3zH0R?aujMF=;WA4^D zTFsXZkoqB?SdQ>_;XD_O>^igyI3(4#+myB*C!!) zr*f<5VWdPyJCw=rIqxL=-yM7_^iuF!PHC%2XxQU(kSe7QF*o25Vm_92sX$Ho%6xoY zlc)1f=txF#-R~304E``5fzz9(>j?K)X!5u|+tFb5LA=e%l!H9BEF0U3KdU>>ViP{Q zh!%pm90JV6>2G^Q>mxWMLvi^B+zbrTc442@>uEAC=sI`CMizhm1R%IdQe0Bwkgq!t zNl{=PWv$nRN=r@ss6-LmhF%=gC}+)O0zd+z4NY!+@tcmU&Gf2MI#`Sdc;3$gdf zYO-^Efq7P<(n}oX;b!twRxhkLYBB`%uj;qRjgjo@;mR%WZ`S}$fENAd)7fAJ5Wz4# z`+eyMwvDl>EQZ{bgLiD^MSz47>OYU;mwAEF`3br?GF)>sCW2;z40&ld$PE>zrC2xmEW59E*&}H<|Yx4tjTFjLW<4qw)9z8$YaJ`d1B>)S6RtNh#w$M6BveJ z)*d$<3=LqqE-V%pk-0gxA!3QR?tgMCZJX`|rWL8kiMYLHCgE@QpP-|Lg?$Yyf2);A zm6h2N)rWm%tNcdAHJj^)Lnnl9s^p?k={G=!9|V^>8-)@VV&LMwiogrp>)l|am)^EX zKle9|#CNbQ74Xp(lERCj_kP9XMYD}8w+m-QMulN1H(OGBostSrhZSZZ3oa98GSwR& zN_`$cPz)0fB9@!nAV5e+xV=nE4EerT5s8kqc0Lt7uv)13qE-MXKBZ%cTbAJ9duwHG z`@2#cP8FTekHhdvpvATt=;rFAI<6Z`6p{SX6-(+ zMZPlmsipVglqnEfXDhCYgD?Xacn1wIf-0xw-yKHoT32F#XJ$%{eP9Bo`!J*CR3Q&X zC~%o&ev*)VeC7XEQu2LS)K%3R35jGX=8OXD#e#Qn8|tzNLQA?^?lA@i| z!~ljYt;hOjFHUlI_gFXp;M99_n8sioO%_XcC$_+;3)vLEn4Uc)8cD)IQ94{ktol?< zcYkFn5P)WhpWEC}co=-RYG|Qh!D}gr_kZNxa6GYjVBtc3u`!gzxo;On?sBDwrI04b zprVQ(ZM_JWul>&q5-89q;H@Y~iJoKQEGTBiSF#q&bYN^t`Ui{^fAEuDT9a>R{e)nt z=S@nJB^A(jwbIt#EPN7LQqXhq>o!o`(g$5iYd* zMpjS=(o0koz>OZ-__(eG~gwf4)GgLdNy(ms=3Y@Z*?-RWy%1n+d|^ zM+Nf$g;NR@{d%vX34A>VKA#;BzLv8_4#8YK{{Y2mxG0MFTBDtP)vAxS%agKsHAO*D zfCFg2eJ-}>8{6VJKAkW1I!dIkQK}hMt(lZ0^4imMw1z6wBv9eK2b7HBv`3Py3w+0z zeNWcOuwxHGW^m~g9>>PoZbZbvBd-4wLaZsi?(Wb(Ohr8q{5^DuF?iuY9yO5*S^2+adPxy(zclx0|=r% zkk#t)trY+S`^NqwUe{Z%ki)$?qV&&9yqWHb17jI4#vvaa1nug9A>=^s?7-WjG-HFg z*S$jx5EC>lkA+s0AWW+s6;LcWrH!u5b z-ah~`>Dy~Ta~G?HD!HrA2q3p?0jNgs%D(#-rF_I?k{j+yx7f`I$G>itT(q@Uvg+|6 z7o3**WctY{7>spk?w!QVFe)p&)w}$pw8n_=O|Y#{ztz!7XN331QJ=fQyuSW^(#KpY z%kp{wg@qjddztj?T0^dTgf5Svm<6|6bzKe!ax}VZt=3V7Zd$J! zFI#9oonvC`klff)J9DoUSz%Kovv?VsRS)wcU=s<)e3Rdq&+T^6N3Euy^9qJzsGqo+ z*HJ#Etq}S6ZibBF0nh`S&)b`+w81BKn&A~5piA(81{NL1)&CG%9hx5PtMW$@a(nb= zDEw2u83SWpCwR^fnt1GSLCWq*5mX*srV&4u=9A9PV)u_xZwX<3$>>fm5&XB?7oFVC zDA`)w4?pZ3Ud`?$m@X)RvcPXM1PceN&hdfW6XDAXk%;i= zN@Fewsb=}idHM32H$rv`KeanmU+Tv=R6IC6`sNlFd+2F+neoemh!_v}Z+-3r?Jr?I zHB$^!HV3CUafO~n1#^@Xfv+R$W)JE%hcgmDUSbw6brK&cXiPJ(u*mxGeOrsEPN>rz z%k9L5aS8qY+p%Eak0eFg_qft5t7u-?I4766-TO>|3Th0R@dg($il=L= zAbF6sDGzr>H7Xr#Wi}-%{UIkbHzhMUaSN&-a-admB)+9gtN@>{-2*zCrVXyZrX(29 z_w7#_xWt1x;Qg)=c-*8XHb-Asslo?~` zK8)S9ae~o6_*DTa~gBz<9#M#`@{F11(hBLLg?%P@oC=eCI2U zMVaN+?Z`ge1ap={e^uMBQGcUCGm=2Gn{8~~;Fb@3=YHFgmD;itgYyEkEey!csN{pN za+9???xn&$K+Z)PkvUW4viJ%_J`zwhVayP=)Z%cNVj6G-bd%qq-K@pQW=Rx!;vn}} zQm#1{adZg|rZb7af`Vq7&VUOSU`c3YAX5(b8G$#}mHn>7y-PNmQmh_w6k&)DOlx`S z(`o;-Y1QhR0``;WWoUHMq;^DuN6HsGJb@HqnzHMijkDeMo33SEk~=-LJM>y zfO4O~(57FleMYA`;qK(Q!-^tpZDr;0Y<_+OF}>eljHrkG%$H>M>Y&(rqJ|2V>IrqT zzqJE#kn;y_2?^D@1XxXM^FRxS8Gx1a^!XoFgYoh487{2=(}MTOL#e6}l0g;2kYD$r z>%t?#(iy<6KS#y>kZb70%A)+;xBYt#r+0i`|K5dSUX)@Z#&c4JLtm zG+L%*;vNDJCKi@87g9A5l-Fe?S0;@N(mhGb0B>h!r_55GP0^ak5e;+?;OiN@Jyauh z+;PAsdh8o5o_ya$HCXnU#r*l@xjCcaLo}m$>v9V;qbBDZ)6&tloaMe2HCpVAkGul-uS~Xx2zEZ49KaajTR`>3XJ&99XT%1#2 z>^>#U6#Oo}SmvU-t`<7HQWXCni39H+>I6_Kd^fXT`dt?Uli0(_<>c)o23r%S9Y=7< z;mjUESBhK5k9_6EE@r}o^jLHuw?96$ zz60_jeAlGq@sw^jwB98u(jX&9^ZJUm_`?qE3=qLo*~A&PZdB07_5AnzZU91=wFZr{ zN;nSIb$B@To4gJTj2p-4zW8O(OMNY%F5h|Wna46*>oAQ^EI3vAQuGniC~O2`etS)P zujw+Y3C9EO_44^1=5xCk5op=wBSRm@r$40+u^k|}<#e%#?LKP5`vkA7pGAI(ifY;^ z%22&6Dk@^dpdi&e8=bln3aJ{DqjvS_bgu6`i z#4!3T-vLda!>N3KivUM`)tp62ddsMvv%_u)x+)5K4k-U;ASwvvAX&dcwL)DyLtR%_ zmwvhKbxKw5Y#xuULnU%p^sK)3X-_YQa>fnJe+w(ujrYT!=vuA>;RO4Lq`b3AyH?x4 zU9RePxE@AYUcpqtc8%k{PEY~-h2;>yH7(WpABzIwIQ#Cr!J>|{OpU!6X=%@Py@63O zbr9rgGh=VMoILiUzUSFEh>dXJZfl`WRIt-z$w3PwIP2cn5sOo`?>HviBYc$mIXoMS zB50M1(9i}SuR^&j2dyFF>S86r-wRT!7D4opNCog@o%YoRLt_G@L^a){V9Sr7*qFHJ=-Q_> za)lHq>REPG(+=nQ-o)M7`3_`s+g zz+25tJF|nbHpPJ{=d*hL11!Oe`ad)AeSMuoC{(*E7X5eEuL5$P%vf&}tySZ4)@*8l<=19hL<{a-oB&6mHS8Z?vjsVr|qTr_Efa|-$J zjOW=j)KGvc3VCy-r1Dhg`wWvrhY|9chBeqw#D#Qn06f07_6!6YO~7>JF0mqO6VP2D za&>&zgh5BkNU>36z}fYNQ~y)FrDMoV%fsMdUBjcyI99J+=Xx9!aS3QA%NKBq7I2?| zy1UoICzT>d@a9_E3P8OJlnnCLUR`31@c#+AAU zfG?rcNKe-&0vg}5HAtt$UtUg`@u~@|Zc*TETG}+*y72lY@n5ibk|=4w4bIc+O5W1i zMDY*_RL4^LJ60oVdwY83=`SKvQzMry*}Z@9@UlOa5uT!FZH32gfhjrlzi_B? z-o$4K+-a&-(;H=5_F%~Eg<*dscqD~s-)JTKtJ>}5y0?+P#C7dG+~x6;#l}4UID6nE zO&OBE!(iEue--3>?%ws*(jBW;E~czE?~SE@TmQNbg1Y1Srw85-%)4_EqK8Og|1fEf z%^;o!1nB}}+O5Os^E_ib=|Zu9EIuYevWzln&QU-G6IuTWmypnLhlFUL3HTBilQO z8$c$l0apnudY*SBFN^zbWB_lS#PI@OTW{9wX1w8ZFH`Vf&#zM@ZzIyS`xSn+zNq?e zMkH0xoiA1A|fWhdy=8v{$9Si zF7?CtZUiQ_FbA3=e)jd*1U=Ndq1rv=Em>Qxc{OYB4ulMi?#;ts#e7SP{VzUE(!6zI zV*3KIsjKlMdy#RTBoEP~QZZda7A@2wpZ(}YweE=i^&x=TcBVYgb&-<`&I_S{QEuX~ zCaCB0>Q@Eh=TcY4TSw?W?~gJ12x3>os1Zt3Ea*}5RGDXD;0^xxAHtv8v%Yb_CuhXT z+(Z&Af9BG&wz9&DP!kJ4@ISM70`Xq*-HPIC&EW=?zU)h;$vRt!V7ktcI4t8RjhNrB zpf^F75289gxP@+*RLTH)LM{mvZIW4>6}lKN?{89g~d-O%nBlK;vR8*yxPo@8)|i z?1Be3x3?Q{50dx+T?vktSw`VcYKJB{)#5WU)cij0EeTlm9MRUT0tCp<1ZEYT-4W~k zkMVJr%f1pwvpda#CzX`&(l!8ylzmu%yZpLT_gPu8BfMrrhQ)jc^QhVP^ihn zC$@xvRRGiEF|5P=|4egvcLc};_)NGjsAB0Qt)o6UPR~LuVr|F2y%!!>7;4fkn_7i7 zmvmb51l4{Xeva1>68f;Svr|#QP{@vkhI;acZ`3yKsT$u<^V$W&t>2|xW;`{8IlF;F zy`cpiz{?#rc8+S?3rZ56wvURgeyUYv$U;sxE#~jhyqHdc%m&4(MMlL&)hzg$1~eHa z(&_r^?8i7Yz1>e(;&3Ac^sVX+?jg$c%uVrl6E{AlDQ{dxaH5g_O78CMjerrd2Rb}S zV-<}M#^i5uQIB-t)C01gu0&52<(Ab?m$4Uzi)uPwqTdrN!y%}wO`R22JRtirskZ3u z2=Dg(kOsQj%y5}pB508nMIC>h186ZUf{^VaFx->(k&!#29z5M73oHF4ecLEL?L98q0 z{vf+UtP$sO=DY*+SpTbVxKCPD-x$Ak2;UC#vuM0F#~b zLgm+cG`X$Ls&K^mzFnP-V5F4}!E|kAk@#({_FVn>bJw2^to#NjI`Q+rC5wSmD zetP~Y?_M>{W=MJui064WRpS3ybx|j{Jn(WB5~5F?azIqInlyE<5wyvsMpPnP`Lzs| zY0QAu?4|Mmh6lIbm-SmJ1Zo#eHt@u5wNDW203{Hrd2@@Lqr$I=tON$v4Z1v5VRT1p zcAR_C@1IK{9>9+J`1+3r(v%Ncd2vY@w5*bAI~=G~?1g`vH4N0b^h}0PU${+vB4|0V z9vgkz#E0B&=JB0>yZGO&tctrM$+l9xa{Kvj=C)>X02`H%CUKh$jgE|D8xDQ)P$>Go zOLX_RL^Xm-#Vbmf`;nN0R7mUxENrFp4pX0tMqYZ!#n}?~5b0@)SalDvM6xK;5Q>G0 zmuEwj4^^anq2-whhSxKY!+A`#N#G!#N7nX;m&NKEMt4h???)Pp#AIA8SdK_vz72T( zyziELl`-?Ms!+K=-(_!}`EnP^Spn#r1J>7db9LJ7ZD5yjR;<4l`#|u6lc4y(B&X9U z3^a&UWwiP!fzGNvYOZ3Pgkl>IBfLr>fU29m7C~FEp7-`m;U&Ool#SA5q?YVuTY5M+ z3Af1(>cQeS4(xBrBxaPjS@ebl{@5nx4c05ucS9VRuT(J@*qm^bOZ-7i{0YeYNqAgn zl;#afm!A~)?rQ7gabZWXiW#tN_kYIKrWncEQuwHv`b>katU~=f6++Ru6INnqMIKSM zY0%=}U}DytaE#GX(y61*l9H3JGnz%k5)t7>*UqT|ox?O+1ZGYnK5_ ze&XsXIoaBfSLs3Q`^A)fT<}|a^{*|?G4q?cJWW6CZnyhz$mpjLVulvY(n}V1wo7Gk zn;O{yzKA%kJMYUJG=WPQ|I@tJ37IZx(Q$j=L5<=UY7nZ`hkz`fBh)uC+iR0pJn7I% zV68VU?$aj=i&RvS z{(7h-f^xNsxxpko#!wKiFcwu*5Z#-G(MsF%w!Af;y>YN3w9d)kbI1tOYl{CvN=0W6 z28yOFZ-gre2nhKz6lsONk&}|nH#d3%BoKMD_wVn}nB|$ZH~Y5x9=-h5Z(WOxgU!ay ziuv;-E}H*k$b*q#YN4Zw*1+o?$<@64Jn}ViV3>o2&aDpZ($g@_oI$oGD5+@1$Czpp znx_VxPU?w!<}nM5dnB$N99ZPH2BALlEu~%W{)`ARsNH^_>+Kon7!)Ur!d{vbWMdCn z<`Lb<8nlY>5dPzS2*0)`ED)GFiA$KfT^lp*?;iDBh`8+E^K&*FJ(ZLgTGVI?m7Tv` zuir@yPQ-igo!*})>p;LxQCIVGCeurZY2XzFRU6PIkPW>4>Dqu<6l4HAJrH{%^Hl~< zxUY}hy8+?S29N#Quahn--zB4d9Jd|3Hu~jZxAi;I@O$I|w7y2vIb=X(hU5JN`oBt< zDs2D@{Q~MT(|nj7Sw&LA`BT0{!unn>o7)|I7WUe5BK}W4yX=+XW4XE(AhV#i*@3umg5e(tXq~>e)0^ z`7s;V0NvEpP?E%v2+~xqDrW6rH&i#}_OZL_x>6Gi)M~U4$|xZ!)|=Y-yH7*k{Yb7y+ZCfA1~)(&?T&WSc`3WTg}PiaUj^vV zY7pZA@_5i4@K7V!e8?9mrv&(n`T=2h%ekfv+iGaWpE_JzxNn9`JbrIaY=T|^I`w4# zm96#11PU9cza?ucnnVDe`}qCoDw9Rkr2cw?i5%T>1$6Y#TMUKJkUW+luc#@DSXMT= zg#p*r4UR z9lIA@-TH}SmPur5iEI0!p0{sxo-;cnS z4?uBBz;3v=#a2KT*j+i?K+_*VD`dvkxc_HKx@n`>>-p*|fjgOm?+5rFENGd6YlBr? zvHBt`UEZ5inaP*$Rn(@kJQDh@H{<-#d8{iVj2AggidEBp-?CbtCI3+m9nF`-)b*JT z4o==I%%dp5EJguGL||xyhwa1{vik(f1+NSpqf1cR%ic38#2HVjX5`>K=Z-~fxfsab z=>pQfDKo#`&rVKGooBDr*h?lVaWdp!A4BE6Od7tF`7*v4i@3n6el|@S`oW0%myy=1 z1u4D1TW~;PpS{0WtAD+$ta{nFKi{|q1K+)V35hN%D+9VoRJ&B5WqiQ1f8PJepQdN+ zxsLyR4)G^4b?;^Wc)gaF}v4Nw3VK-)KlWOZ6QO*d%kYiX&z!51TMY|8y2v$Aw^v7hLTIjdtx9ieWRKO^H82?k^XXM0kB?6-{OO2^VN*y z$GB#arN?K#_L|F>gS^ihZ_8(FIC|#{IoMb^IM_Kj>K!Jg&1-Ab?2;1a=jTPb&z4#D zVle3F6W5aVS+Ez?VptM>&+q8`bMuSe{Uea_eO=xb1ajpAwo5$s0yf7^0V536{Zo~2AeW=ss`B-pAtuDLc6nc9hEG6%hp!nK1wg+cfMzSC|A7wnLevug$fbo50id+bb#i}Ym8O9-=x3(K@Kh%YBh7B;<+y;g9D%I`U!K?r>V zj2PN_eYQ4P(2%^|H946Kh;z)88cX(*&hXX}9=y>p>{SmEK3S?3IDi5bZ7CriAMIAI zr8~C2Uf^+z;Fj^3_sMT-!=4=g%V-)n86@?CBY=%juYODbqdQxsSIe@fwp_%HEkpmx z20t5nmkAF`iTYz!LBS+8Ge*g`39p{`o20}C44Dd~809~%OB%I}qe<+@9HYQ2RYF(G z_Zdj!UNzBi2yITmb+dS8YL{Ii^6wepEuZhrh&qi&Vpa*yFEnW17GSCXh5QZ$1qJlb z5R8zUfPmqZm7>wAE`(1F=K|RS3I-bGCZLq?MW!`hIr{5O7b{tt=%TBog;9Y0yXVg> zbnN#UMoliUS{o&-ecsIHuZ4sJ06dY!7VpKY+fQtO6&KWL+C%GNI!R~po=qGYyJ%`S zXU?;;u?w?Z?})CM_z14_WXyBa`X0THFg`inviRqea4wV|UF*$W@&o+e;~^Z$aC(r? z@liXv)C1$}g2YV9qF6v-J4uck4Y}T`PsF+O?yc$FRzexc$HeHUD00047GGW+Qwz`H zRJDf(j|g07Yjf37c+~~$>s1J!t#DImzc*$+4ESPfl(Wh?A$~HxZg3yUBu@I}!=Rnl z!^8?+nHT4RQ*D1o`AMr(a=Z`bDJZFJi3oUt5OYo1&n*;^u+KX~LeOt-fWH9jDMw@| zqN9`VSFbSV0>0rsokh&!J89QfK2KpL-pz=GXy(wv))r{_$JmCteom*PBw!L80edof zc5(TInLO)(sQGx;Se%sDe&>}-#m&W@S2;PffW6*H1$}nxo5?NhytiXU;(U=4t>NCZ zlQnp5s~5H=xv;R5AmOvl!n^nV?=5*n1oX(B_9&z9kqp=$)Bf-XJF~9|SV0Uj`oSFk z7?=gnJ&t?)o%7@B!-Sr5iuU{W)v8oOe&9q|;Kr0rFI|LkZ|Si1LJ zf%hrJNTg)}VWQ9^s~b<4g`UIL66}A>Y58Ph@C;sUIajT#rWy=ZC~NVxgijW96~)j1 zxxiKJ5p%a1>djCB8qe*j#eP8qeO+pf?p?L6VU8)+d~5Z~trU|~aYTi@_xWBn$)0UW zVtRUNYC4_6ZXkFTT55TiE#z=A(U129l+A9be9sVddU>Ej4lQE!wuSL>a+cVwvDYv8 zy!0Q39L)FBIzjz5Z4zo=YIfdgTMZZ9(o&n~4taZjw?}H>#oqv06Ca~v?#^eCZ-Ay^ zN9d>=)ji7P{(r&IiA6r|x@#a}aeRGOwW49tg9#G;D8cuhuTmmy9B?ij(S<3+V@`22 zWK()8#z3?Fgl40d<-OIIEe123tr3x8#B}fGEa=VYJCh7fYuDu0VJ>f9@G zAW?ge98XQTN@18vZq+UI&YMu(5eDCJ;gyw%Riv8sM@ts$A z0M7ba^13O~7h7o1KK1s>|^n(B1I8yAp~o;cs% zmF8;>&CL&QGKw{*Pob5vwGNrHBDDx2S0f{%eB~I4Rvp5 zaD^yX^`8L5Z~Kj<3`j2&fD^hW{(q?t6j4c-RYn$W_dPS4 zALji;Pi_D7P@mQVny(BjXN~#3G5$)C*d2lQ?oVdT7AKlB*oC)7Bm51@&Q|i#8{>MW zNn5&UczUv_nrD7>u_}S3M-$qQ_wkPBHj#`hL)0#PUZT*v<=W{b6iE1h){g-TgU``> zWJDn=M@qna{YwRlX5XXPBh;-mt@uW+{N-fB7uKqoR2&s&Bc$4%k4i6*6TtX(g0%)d ze~w=Q1H?l-StUH3{SDz>nJ&!-A7Y<5kt?hFlr}$RZSZt*pN2?82AQN+x+Vqa-odj zSGb?mRdohlq*;-wJm<}}hJhSX^qh z6Kvus(g#5;cVO#X1-d-9I*Eoem4g6#cktCpadHaT=5wVdL%PW$iuERC6lcG^fZxATKwh_VTe9_wpMf z-nK_}G{n?g-?>G)WAGu$FjGH!H%QyRG7hfI=sBU$<%C)Jl7FT!3x>=^;W~&f zyd;c{G$AwlOT7U%!bgVFn_peJfPD*h2Emo5a`{_Xs9w`Md=%>7y~Gj96b{arQ`6X$ zMrfU~^TAA!BjxG!4e+davmfBVWwh+!l2Q2V_t9W?( zhuhv83A>Ye7RVuL1!s`zH}P!adATM%)W7z^-r>?@XQ}e(-i4Ei9jqu*52`tA*edjH zQg8fA8u7t);Vk<-Hkn%2G%y`Iu80WXl@8tr3#1CO>;pp>JLdLVX50u%GEw3;-ygMo zijA-eEB38@O-YMhm3pIFy!xhjUOxYPcdBz(F5i+irv*3;-C!>NM^(*%+2Ub|9lQh@ zW*b#sE<|u8A37!mP(=8De@{B5hQ4pWY8r*Q>_piwtrw!CbRbuQ8Vol`m{uUIEUH5m zWuJ|W7SyP{7O^KY`}0^0IKCh)m@En=&66Pvfn2n_WY{WU*608y7b*AG;?i?BtgLZ*zFPQMKsq7I5Y48sRY4zWI%^=AXRqjcev$@)sr9uGnGwpTgp}e^7B_s!%YWGX44no*94jnp z*Hx_}=tnk#*j3)s+ID7uBN1wF;6&nuNwQ!lM&oz!ijXBQ1#`CM5f7(tp;8o87WhyW zY^3Ri_WII42k-2;WP{7@811M0P8ctLXFWMyj^&YVYMm;v+`2fB z@UmT-%GEGbrEi%x5BVDF)MDZ?L)7dyVHn_w2HQf;jluIwT2L=X$?l!iphtsDjO;L?o zS^oR7D7*27W8fNy+D#(b!8bKKY8rP}^$^Gcq7eKU1scyr!L#(zg(1Y44c%PC3&^dY zY_I@w2KIsY!L`>V!*GU>1Jd56vTisFe%voO;azug4c#H{m3D8Vog3dbvS8N;n9g@=zhk@JvI;v z3SdIzbJm^pC9vGIJeOZ_Rl39Dr4Fd#7|vl{@fBy-qEm0uOk$xjyX!mcO`|gFei9Ez zpO3V)$u-yCyZZR?qkc5wR{zX~I|#A=>C-3R4KA;(Vbo}Cwp=cer%#`TWpMsPbVkN z@6KJ&kUiUop$iF)BO6zJj(k;l=U#YrC8*~bnCr@X~11KH_38t zr=J&mY=6U=e%`}=Zz}KOOooQa-Fy*jRHIaHC{6fa# z76*y7gLUo%LnBxB_G_8gqc?S#Tm4*}-@E)FjpQ!`E?{G2d}Y)#GWaQ|n_hCyRxix-i1+tH545Ov}`RN;+b8lpBI{cxDebq z7zWtcvU;WC*BQ_{*wf;i#q8->BEXQbvZ~J(X$8cMwX{YT=4uS<9nZ(3V)nteP()!b zgKq)gF794mwgc$bQh7tVEgK3Fb-IXy+}#OG@5hkE;8qHdzHjGH{|VJqhs^vht?Oi7Qf$-dEE-p99r(0g>a z$rCESnVX7{H11V20WHKH61U$G?9-T&NKx!{D=Rf~qyxf) zEYV6-f(|r8@zJZbT_%bQ3Uo~em~7)cz0R}MsBxUrtqHJIwK;9 z{UI|y>}q07fWn;%th3a3i-;cT5pb%3G`*?2u!rWh=tbz6+D+A+J2;sJo}YUiNO%U& zKi;3Jv8l{*@&2$Qy0kr6ZP`@kq(a1a2Oy}W@*E1ATD0HMR^?wGHHipn-*_&KONime zZl+FS{)U%2{l!&#s zniDt{$=xXD7t)JldUY`aA2i+4Y+1xfXfzi#7FJ2QGs(PUDRuU~SR1J&mMdIHCXk!^ zRg~uw$!-H9{0ZO{4Gs^7pPcY{&aE-N|qQs#qG~fx%Ma6j4~f zUU|KQ$2Py)YS-anQyhad&6_TWx(Mz3m|C?)10lYM@R9s>p99u-Gs|orZPC?O;|H^4 z?t8G@88RWf_{u)fcH-+?@#eH1{8OSH2aL8d<=K03BzUi!L3%b^7HCNXRX5uLv(Dc? z|9fb&?UUcKQK{=V5A?$F2Z(C@Njid=VWf4#`(jm|=A=RXc^5mU*&#h{@h;J>Ls!>x zTitpckEvw5>%x6>s_;L4Up{&wKSSE_i~5fj>2g{=(S7sg(}e5TFF;G|`h3r@&hZLh z)}gMl=9$_`2N~brEiW!r3OQXkkJKXrFT~Zs7stRk9GN3UoJ?J2THPaJTVRK^UK`(E z4}JlQ26RnkgRg*rY@9C>rFwk)yX+e2%_jp~8W|#ir^BMqS_d4ez7zj(51dq{SGi^r zW-narA$D75fL>(T2b@9$Uh13eY(f~&@-j^j#W&FMx^xTtoB@3S7bY!lm z$bDojN5U+J(7I2YY4O1P#aw$>Qn>6B`}2H1d7=QXlhAu|;Z?8tglK$&nH#9)(TCp6 zMpOXD!aM+e*x?pm$xG+n3PSm=2$`nRJ7G7)41q~D9LP0S|M$s+bpgDhwji_y=UnWy z7=x;b(ZO7J>*?>al8HK}ISH5F3&czQkU5|;sGjvB=s-eMjf(HezAa@qL&U$<3IH(^ z=d0&~nt)TBxUOZ7F}0uiP7%rb4PKlbBoTl(0;DY~-qa4jV5qK-8 z7B4urN)&E(#XIu?=x@M3uL!V`^}Z1h!e0`^l8`U(*FK_O8OwX|G>u2XZ_7%o*wa? zIJp}V10NPcD2}p*SRdGo#muQ1JF|a6zxSQ#^|177t_5UAO#72E>_+M%f3ykxp!L24 zLRyjg9=KYsa?)c;H7R|6HsC}uGT$&sh4CkW)u`^rgGLf-5d_5tGj&{*1-0)&3t{rGpveN`6(YBe{S59j00T*gJ+!TUx_5dJ8gCo zm0Bh1oh8YrscGJE@^CgbRH;5Tc==ziEdCZ*y&g32`DDx9KHye{fTq9@{>81+K8_sWZxbFFm5O zulRqEijk?U(5n9Va~BF&ki!{$^3vm`*Vx?y&((7E4R!z>*d3mno49~sM0}0Ne-TCc zUIzIOYts-RMW4qWKS;7Z0-yLY^xub*f0R$(avvH7%`ux$46rw!8IwPP2)*!O{h$Ag zBp&1%nD8@yd+eFCRQBYd-?Lymg$MtQ*fgj2)u_+*uikJrQkjzf8&L1yV9xUKYo{|y5la~0j&uR9sAMIr`FJu}JUa}$;Bh=Y z^2AT(5?N;QY~q(m#*N=i!Hi}7aNkizAZ3B~)8g_ZfBGdUskP3cQ?IzkZJ6v|Ntti? z?Q1Rn1bXCQPyTyex67fUoN1y3z8#%8N#RH8bjmWBG+F&Pd?Tl{UnlA}?B#=d>tn7q z4jP6De!su|eI@kv8mB!3q=*kS;GWW1{EL*BxzYbgU(0p4PvdAsIGzGO{a$xccl`~{ zyTS({-#!A-`lx4@ATye7C}yfF1Ej6%M&Wacf+6F7&epK-5pUy^%Y5TOZ(^ZuN0OG5 zfokwanz+tEOk))(axybv)nL4>E3@$(nwxLA|NXunPffVCB_tjRj&M`CC=j7Kt7;u) zG}Y8Xhqkx3e|he$#x4A?d-P9=(FS0+qWM+j^2z76ew|mZCUfL57#SJ47rtq;aD#mO z{kbZz(r(^xCoz?U{NJx-2Czp|u6+}8@Mf*m)YTxJd8l_ZS8>kRz|<5*Wg2h{e6k}T zuSG@o`SSHQ>L2?ehAk@k61Xu3a)wsjDhOpuUw>o1p)&733u`goY!Is`I7u)L`ERf*JT zsabrlvdYjhkcR8t=gCsNxU0vTqP{L@(`}dU1H5yme+KY`RFM$zk$cb|tJm>KqM-Hu zNMK>Xg&T5&iDx_MoteDm`0Jf%XLbFa@%~H|0H_V)-~7WqaT%!<$CM)fzuhC_Kz12JBS`#$-w8rzLU-n~f z=uE=L)zqFmoPPX0*;C|2u1L+vM8?6eyg zTt3Lc;vwalp|+u3>+67LZNsJluLywi9?r|GK4bcc*RJ^y-bC%AHr?B+vW=s^6^fO^ zMe?{kK>E*{2YeTI43K#BSF)%m;D3F76(A8Hz;~qZJJ;O+XT+L8EVCMwtU&y7%|PM` z(KS*JfqGbvP}v9tZSR${yo>wY1Y}{kA}wq1S%~lJRpR~6i7Y>gxfBR`T8PlI zX=d)Mik438#kVBX=TP_FnAl-l~=%!>{aD&}PpvX?{ zSVqSV>L%<=cN2A^DWizPc;u4tWE(|K25Efp%7mG$Erd}_qh;Fr_W zC8Wlt^Mfx{;hPR zUP(N3H~unEnBwaKtRp!<-$`B!l4JzuOpGBV>6#(8OP8B$trDeGSJ8Wcx?3i(gUN4B zKyAj}0U4E#+M0s;Fwn3e;jC0}(X3Sy4cw@@r6Ogz&IE$-efRMhhWM$6zn>Z(j{Wp|5HQ$?bK?&7}A6 z4VBQ(S8LVb|MzoP;pPjn#Gwv9K+=aT$;@nPRqf#9kBXd zvlkAQKDxU=!Y@zY;w(Q7;8?@OY9*-ovWBNGe3>8ORcZdgd;t2|wQ2a;nwL56rXB=G zg@lA4(C58_r@o^j5*|K&_$oZ3Yd@hfX{x9w{ZqJk$kq1%J727N(tm#0kMa;ZGBX9) zZnpZInmn9bo=>C1wm`A{Z&6h+n+9ius#)aWi6$ItXQPFxK<}{STdO>=hs1H)o(khO z?nM(ksM6RNjK9ZUoHO@Ra(tQafsUmx{r%tI2{{q&z-i%ldtf+iyKvUPE$fTs{urrS=F{Xa9aZ*wjr!f2pz0BRX z?lnK*`9JsPH4bCgZ@w}g7@IkLSdbj;zcE0@&E%}t!}{ZtmFM6E!^0Ltyx{55?~Nq~ zJ=hv?Gto{T20hUd%o%>`9I z!BxpAqORh7D-qu6!DwDhpI?8|rjXU?vRe`{c=nBPhF<*L+s+S!!kGHi{MmR_iJxfM zC(9nS@t4~Gc0FsZjpWT?m!~gLJ2D_gdzQ{@|LEUHN)%O6{myiAjWObt5Bz}!Tv|J5 zhHCP18-t!$c67-Za#=ffwR^P8d-^V2lSfOv+?rB-R{NAO>;eERpI)95m7L3~_1?_K z*l33*XZPdR-Hb1T7oMQWAWqLq{aq+c`7nT({t+mx8MFq+P;+Xa{~JKLG63C=rwvH_ zC;NDmZ(_7mcF9f%)ckyLa3ZXpoj%nF)1>SW1nR|K#Dq7U)dG(W+`awyRQcd7K7*I~ zjn@pmIf~1-2Sf{76t1Qj2Qf(c0|{BRcoXK@wAcJjFFsA`6Iu=CxhFy9@0Pd(4P~wm zxKIp@?&?e78cGJog$+M{HaPa4oC4G6@Kdi-sI<8A+pY2~U6?8%m$@v8w&5I~`!=uh zh6qIqL*!Pb+=~oGx6><_Dkb=zA0Gyhj}f(d7EBl}p`(X(p+>AQGJvIT8i*T zilxEDeXAuXY4c1)>!r6?02rxwSq}x98lsRU&|{d%o%CigWIqYc@0pq;i!vkhHd9s} zKQ~<1sCZNyo&DLprtj`$+r6rdvR6C0D(4peao784xN%%bw={&F1-xB08eKy%3FG&) z*ZvI(TVHF&B#!^6Zgu;5$w1yxuV@AwR|Cue7U2}G=eWJzA495utNz3ZKm9wPdv0*RS@D%Y65cgpYKZMxTmx|g_{Ks7ZY|g(C1_@+)W9H7B=GzV6=ZMOt>gm(rkbmDLrG)U0ljqnd%98b# zja}&7@~sH~C-eTM8H<;-OJ1g>F)kym)9b*FgObyZj^if5-zw~C2UA9QisQ155-vb6 zi>Pk}JZT7clmJg%Y**_zxzg(oHsYB^b>Fd4lclO|d0R`VN6 zy1)GX?U%rR0>Xbn(sw~dZs81J6$$@A+jqr1h1($}mI{xXFXgU`O|@X4Dj2do$aL|Y z=^)`^u~PFB9!UQSzn4&h8Kg7U)t&2uqDGa(RWgSY|J-N6!c1uFUn78QWC4VgG5DXF zYhIDuzmUn5MZ=-+PJ&3BrKECZ>D~EaAj(1V(BJbkkY~pG>Ui8i@YMABnTx$}&GLkX zD1?A}EeSr>Rj^KjvbB z{dP(9)?2@*R*r!w?~&WegI|#ISmYb0R`_R6`rfSLYvHXzPG5-YBdxNc73KY$z@-fC-W-(H^=ii+uJ0mHt47<~z* zNBYT5W!k}xfq zS^bIvnsx97KoR>R-a8(Er4mc`r@pN(BXqDpOrHR1!rxUTd?;Aiu(zEfZkQ(-BSrN8 z=z7b5xRz~elx`e?YY6V{PJjR#g1b9}Bm@Z(TpLetcMlTW-7UBTcXxM}w@CKh_ndpb z_qTuaT2*US)tqCFF$&&!u$jP>r1i@`>o;2=q-)OjapC<{nvVBF@uyKo&s?np?^Uzh z)czm|!p7_fiG!Br4Lg+mC##rgZWdmijeeq|rqexOi`%)Vz4D@)D%r2o3a3suxS==^ zMwxx|=r_<2;>SdSK#`;>;k#ddII+KdnUyaRmzjuySUPrWi_qTtxpnnGD_W;g$5~Fu*sIYlP?`yK_KK9$0!@gZsEqBN`@6JlK3ytLzS?bB{%fq6rehfz2a z2jTJ1I@KqnW};w3{yRF%Tl3f~Zp%KmoP=L$EKl^*X2W_O^@k491N565bg%liMb zp_Go5zA7RO2QFW^JUi>?4tD;e8YN{X?=bb;L~BpmDaV0L>Y6JZaKeF&Zy_3_!2Y22 z$s$-6qQyq+K_Laf$p{_ac+-6W1go{-&A8Pp-zPRBt>rJ#IS9k=rjyYhgL6zWDBXP% z&vha$SF>~eqa5k) zXW-$9i|frtE;X!Z4+O{tm!0hG!Uy}lP#wy7z%&SmenGg)>j_1C~^R`n< zpU!Z5cej>yak)Rk!OB_+T*@XZRKVPNKdAO*7;wbWsS;Itu>})9WzqcM&$F>xTK`fs z;Oz6L9Qh1t#hP3^g><(x~gGN(59&yXaqqm+Xd9EP_+!j z;L@s6J-QllgLm()En}B7{X;rkBti*$4_%|67(|KkT)!$X6}Ay22E3sOq7efZdWi#T z#5GdqX8rY(Sn<&LX>_Bb2976@wTx$C-C6lKzLEOxrJ$min5s?aR(UO}c#?qC_0h+6 zROJhEYOi3q@YS>k8SBl;)o~I2qT(jURV#xjb9@VwWR<^~IH$g#cSIU-+R>aDV;=S=rTB z>-T9UCo&l?@-pqJXM2j_yVY>;b-A+-g@$NY1derAF@37p^{iGW$ z$2)q*oI)Whs{7ZQ`zz3K_0{#~63CiVSzjs&9u{M?ZR!5+%N&kR<0jwVmH^VA;a_Qs z+Pb{f3VDy`YnNZ&wZ(egv>cP_hjc!F6mHlAAE5!l(e6|@&t$&yvHo0n-BV)R$tJmJ z*Oboj0=?}x65ZFYk~al;gNnMc_e&OK?k~ELyU_xgmiYD;LTlAcOB|GCnEn2op|3=? zf$TuRA4U_}JcS@YEOO~qaE&w9)(MUdUgXu3@p1Z#^Q z504Gxj6GwrrF6BHr9Tl^Df6h}M8UG?pCR5Mq;{fze`Y`V5Sd?eM+3u7pLIhY$8Cl# zJOpH#&sioCBrJoLY_1E(Q|T}kk$u<)2&YLwA6Pze&CO~4`SR9thEfnYpMRjV|Nhf3 zylg=M=C__^0tL%sMO4rZal5hewkz9@J}(b{q@<>9&pg#>cbE=R!UNi> zRvI|-*JkMh z%s9%wc%=6T;@>M+osa95Mb6p50vMHx%VYN|E7WPVd=8YE#Ae$g=RCHG%xelF+TwV+ z7U%)hs3$HI4+8ZzlATtA4fJt5uhW4a2_DE7VuH2B+CmnukA|7s{(M_4=%u*PxTXp? zcWb`u>ue>10Q=9xliw|!%~VqRa+l0r-rmgN4JXTzT&Ov;IA&Ef=WDtz-_;qMOD0}+ z_`UNo5^QR(uPXkLQ8XSY^A9ZOVTK}jcuJ`zhyTyp10{xkvAoBkz+D2Hi%O8e>7tIt zyp=}Y5T~im5V#hA zyw&&od!|I3mi2}78ZjMKrZa+Es}1HGu#|foT96yK)6k}EjD|0fALoXSu7vSC_|AJC zOQ8XqS7=eRL&U2kT+}C$Mfg2^zFHd2>uJB^{!4pk2Sb$btXN$nMAqNg>=i|AflQ<- zmk|Jk|K7jipT_ZP*y>JTlx(<^$LO4!)EBlJDpWt-+Pbdnr{HD(90*x1M&OW*pQLNO z7pR14)B*OX#`Duxq@ktJmK)AO6%`wMW+|pRY)nIQP}53|T+-%1!kH@{Ynk)ehn{F$EEpva z{a%^`TvHisVc^c_a8pF)Ra#-L#QQzj;o#oG z#`NQ}1FGAxP9_bXVeMrFPuMro**azAQ*>v;_#DHX;<6AU0z4xWs7(eW)~9+#Uz`r< zX`P%IJ<}>TkBX}7xU|XNha@+wcj2NuW$OgMk<+HF4kHq%w%bwebyS0Z--eCOm`U3@ zPBRNGlTvvw;ZW=PCc*MD34gFTZas43@`u6t#%DAN4g>TeG{wn)+v^4aTBY~kuPcap z0I(r*plPSx3dwky2{$Z{Spx4GKd9u@Hd$eC21%qx;7E_Pg88!pqnlhfkb{baD!)%* zmZikU*Sg)<_S*1G#!?JgzcJDj*G^!Ce$QjU)?|rCJR+^MU@5MVb?O^5Lf4vt=ihvP z5Rogi@X!e_K!CzGZR7A&5h_v)B!pYBxyE(XT#<<@LsHm$r?`&!sVtxj@cPOV&PR1P zT4b_>HxhY{+q3lSl`AcNmANG(p&4@eVu{<&U))-4TtxGw!=VIx9+~tvN#Okix&7?j z)%Z?4q3?T>t{q$4%AZh@LM!6xQ+m}#=^4NiHpVKRHG#YbO zrXc11{vLp=WZv6?YXDn6Z$()h2um&@LHUK$q6Ztw{CUsKUkcG>03kqHP{|&uNM_DR zPP)JEGM9$=D@s8ucDMry_nX=1T?p_UqR_N+KG)y&|2(GKV-`znyc`WP7sVbc*UMGk zN1c>pcu)}0CbIr8xLC1wne=ew!307q<=9>_M#Ec1F2I%fRQ2!_-c=!{&Wxnhz-clV z8Lrm6B{md5f)>13$!|92;yPQ-?Q#&=GiXj;U0C(hV%c%SV1!p~*;r~!Hfe3s)h;8m z>09VEVMUW69!H63hy~t#g^o^2@)xsq<@R!$56|a$o9^b?T6}h+2-P28O(J^W2KJ~v zjEVLlavc+^xrCM@F1uUpp5o8hGKRJnb z;#i^F<{oouQvEX)Rw&u&H=naHm+(i$*b?58$+>NVVt=y@$gEap6$?Dk!+ifHIdwr&^YvPmR@TvGJMdPeUW)XOfkbWwF#u z6ez`@3Z<69Ya>ekS8dukSM_p>Y4){}vQ+!THpcQ+%Ph@wiSg}PL4kQwd0%SZ9=7rx zA4|p|@mEb>Ig%hPAHH`EJY9rg-asxN+(^iXpO-WSLGI3y%R_yR<*jr-(sO`sHo63r zy8Ye2<+;S!h>#$2Dqq5h-cvG*%c6c6`G&_VmvjCV zZ~|W%2)k5J=_r}?W{&`W5&+b143AnzK(Eg*LRB(qWJHmcvFImgLciWl3K&6%&XncK z(X{tteqwRs#|8`rV3R zfQbDvk|+ytS|yojX(j2ik^pDeo~0z%7%{AoaMiEY(0K(G-M|=zxNF{%L`q}AK^*d# zS_dUauypZ8k$oq-T;H$pT zY?LrrYA-4%ob&wvhW1nuachZD=EXUaVIlcLi##=7Up#Y6?#k`R>Q}GTaO9`G7Zr zwD5iR197_jFN)`lP-u(=QnU1QK7V>UFC)rk32KhVW;H!~XWHntTa$5^cg>{vlW!VL z9DIEiM%fw|4%NWAgKfCaj4h^QgYnd#S&LwM?E#nB1dwJ8?(KpRt<_wB#p=z9^KY6= z*_tCF;4+9l$Dm#qJ|F!K;5h*7GiAh=b=lQT&eh^Om)POvX=>lo)8Cv3jslSrCylN~ zU6o!rQ*NFzMdJe}qa}GNt+TwOs&bB|Toy<`!2Go((OIQ0pW3i?=ine@thu>a-O*4G z9Gurxi)Gpp5|;l3+Y4WCw0UsV6yu3d%dVllkDU(hxL`j8+_+?eD}-@v zCkeqJ134ItQnZSTi=z;5Zja?E0FH>+g-Fq=YfqEN!%twt51v5iSdoirWM{=Ac5b_oDCEUS-eV2iEt%sZ8{}I^)hE# zpq1K10+3MQQ44}!oSdx&u>b`I(9c%*iq$3V9Vt$NEPSy(;)F?EjuBKLthbkMJ+CG)8|$kkDAzTu2~UANikW}ue={>m5r5Cv@>&x4k9-qU;<^fV#{G6H^uI}{}G zxN%!Fzb4U%v>W!aq2G}x8Kf)B zpPHf~3TB9ot-}MtdhEU@a)BPBGdP6E2GSo`=0-d0U;P|~TgHIxdtvJO z5vWaPXGz{2p7z~9QV@eY1|JG_0NO_Oc?@TSV7lXGxKNxtydLqXvt}Ib>h3=i6tw=G z9-ORiED&L(lS&p?ETJ81B%p67!w_M%4D3TVBHwuGzg*6JQL(iiJ&5|jXrwih>PegX zl}~>x3M0}K;$GbztH-yW^sZR!5c*G=-3eT___XX8Sf*5zKJ=%6=Dn;w^ ztLqsMP-z=^a00d`i<`zISYjt0dJ|-zNoo!bjxud21%)_nao9x*Sfz2zP6)f3sR)e2 z_K24~o;Q?CSCN9Xm5IX@x2Mmo(xeG_ZzG@izlP7H!@`EK0N47CL4E*5>)cwo5R@Un%fqc?d zR%R1N87OXtXyj7sj=z3%qofCEt3oDF;}XYC+XsXws@m_D^_y{Vxlt`aLv<06YVkeF zHanKtC+O_#?3npD2l#*|oX>;*PjX@*MzUSSNG+&>1<(gtfBv*@rRq*J8oD(u3h=~$ z{D~kO>kpUwKZ~D4vT7<4y)t8HZ;)4Bndrc!NFl!aOk!XlUm6?R!y0=efEV; zSsZ5sI6S^!?q7wvwivcJCJVi{k?C`IP$47oT!?j-pwIFi@QMO2xRPjwL9LZ4KjzxR z4}Xq4_L27`xu}P?Qwl<2h3XD(*@3Az-a!b6;A0Xl4;aSC0^a04>|9ccY-g z#6O@|o8D$b+@-FJb#|2C#Qu0B&<%kRs^vvxRaP_>^PWxqzC$^o^9xx@dq(lNSOCbM_ej)Jnbt%BXqU|d<57v#UDm~p{UMCa4mFuII?aU?yUw?e@R$V=|vX!;Gq9PlZ7Pd0PYjl%RO4CMy$J)>xWtJiyko|_L&gf#bUN=8;(-?E_*HN<<0-S zGDHBh(Yr*tR~-D}LgqKp@coxiBY?)~Hn~)pWaAJDx#8nxK)t&q>!=?2RkU37vc@BH z2>PNci>Xx^H{(_5QVk#1K^Ywttx`@7n@nHzL=4T`#7kum5d!M`d$H39U$bl1_{g>)pYIh|+_&_q*quXb&P8t$<3Jrm`f^?n z2vpA&EdFRW9e0{+l2y)jQ~0Dw^q^Gj<7BV-fCmv0>jJ148y?fjZY9<2@Gy>$a{dms zPrC1ti(yV%Lw1h1I_2)C5q(fR#RSEdWz<)Zj%s7B85e6Fpi3>RUjQ-jJ6XO9gn5@6 zB;OyF;CkQhaF*s<$?IfjEFWLl>eh;$wvg9>HuMeSDRp^Vf3eNO>s!nOkV5fHr(GL+ z6YBm2egW~6u;z86Qk&=<7>Fid*a8y&QSaT>2k?I6SQ`~DVblmD`43kW0DTi8qrW&= z9hUhZ?euu+am!8?1Lb4Oa$T{tY=z!9L6==QGFPgUQY?YVVWx|BP$F!}4VM=wn= z-Fzfdkd97uUGZ42!%Zm3EiPg0%I^BY#EHJ?gK7}eT0(YC+bwMEM3!Nxfux)S@nIvW zswNVU(gAhf*-4`7l9p0XHk$g9JFV@v%4w7304l+mo&C8w5y;84hPsdY@C6<=o~`w- zn`piEOg4A}=Gp|5pg@@t1;G>QmhFazVS~ZOHJU1OI6w!c!8>makDnM$z(7MrCZerK zRBHgyv*oXa;Z4e-!y&}|!FXS>bicNJ2vZ}nRn-x9m+^suwcbPF=f*GOKb4A zWNQ8C)6UJ=mPUzw5m{?|BZo!H`)T#8m{9tsF9uzIg}rGAtkxNe|0>jSu5BigR|(0b z&1O6+4J__V(A;bO_;EXjj-9ys=ifuw;dKCY*d5R?yEZ1D}tnZb?^~lHoo<(w`T;_6og9 zz!^~fX`e^ZPcMr->1{`EgUnncrQ6`Z6ih)*PA(uI(9N2Dy1i`*aA{UoJIwk8- z(3c&fEQPaXXZ^NX?0Dsm^BV&tz|bPm#s*ERCU` zU-0u!=QG9Dv8vF!km`B=Xd?=72DlGnyMvj1QM)|%<% z$KZiQO}MdpJ(~|*jfAI1V-B_C+QngEXldV;lWqWyk!z9^%v73o_G@jc!k4Xs>K3T% zu9y2S@$nIK{Qk%XJ!SZw&NN1F4RF$xgjWdvV?NNA1>1@nC#dH^lb&gRDM1=&W+bwv zaifKwUQ}37#Wp#x75r*51vpc`C`O7`ML+Lg$rO8ibqHkkdxx4P6E3UKf1ea%={EAs zM1V^(e^q{HzBFW@)U$Jhmh)kLcU`8O#NpD~_b8t~m+K@AI>(J==OAU%G5I=j!@5m^Y7IiMJ7T(uJ-83VS8T4OnKZthEP_xUmf z3(2CH>FvR|60jyRGc*52mHtG|i!oZK#u2d76K-ex!VYY~{z5}dS{g}le^vyY-Hi(p zgRzhg%kW;dJV<>`-pJkq=kX_(Ps!UK1~!h?u|F99BTy({YRh+nj=}RRGu&hs)wf7M zx&IL3joSwF72zT@&5}~x?@SpM;nhi5@+}nR$}iCRq7Kdx=1@-dtlyL_(P>q0CykO< zw1vscqb28??FOZ%@H@e{RJK!$u)LUM!5+IoI6pp6NK=?c*}2mTVG9rh>(Nc?A@h(d zO1r1iw#FzMP!_O8JTVwZ?h6jjT6xJ2Ag@gY_6oTc$nKz>GRe!z4mgK?Y72lhuy;Gx9g7`U(jBz%5)+D;Q?L$_WU z5QyQnt|n*>G;dA8WgDU_%R=uRs#ESyMS$ez(iBovs9v8$p z8sMmJZymDay?^7}>dH{ijGB=q0tP<#ko7eTEK>9CDd$S32oM9s`c3qff&BXd1f6~> z`DCs8xKy$YjaDlRfRHJ)Mo)a!mCmSjoCqtCX`q}s4P|y((5xKk` zd^VEDYH_(F@`S)ixYZZKFq-qjC$w!I@pYft$NC|G)`__qeBX|f>VEij;HW2I7HG8o zp;ZHkoXJ%n8JE#)0tgL-S?D;xK>ZgbYBP{f*y!G4z~XA>$`k2BW-4bL)YTywOx#Q* z2F(J#U+^=c{xGzV6`hm*xlXSrZv4bpcTm`gDaq(nuIn>K>;?~00lYB zVF?DzXi~ne2=VGnF5U4uve&)F&sqxeK##JQ*H)A=gz*nRXthaNwNW9QXyX)~#1_KV zMOxE%v@bLxp`CjXCwxKkM6|3%Mq%?SVf~;z^XO7IO{4Jk@r*#~ufP6c?Y>QaM?!S* zw7mU!Z8SgqJCKY5H-||jWupG1`~p2`Un-9Onvs!ArAP*&&+}nV3)|2cCKLN)0~FFM z{`K-|(KRfET{G43)d$^L)05S%Q#mezfHliFv>u}p99Xo5h|>4pQpyN6*kOM5p zR{?%X4cpM6^`#qIHE1L@EiLU{)F-K^Hl-G&kguv3lN(Bdkk6LV6Dz3%*lhN3Fk-q9 zQYe2DUNl<=!7nr>U-77hd5+l6-OH+>d?=^PQdkS*{y=-p!~*2#E7-j*guFbN78*(A z6KD$&sGgtu5#x}oRi~PsryQAey&{6-1*!E2kj=l=AE0P|~5*Z<22 z+0ICcnD-bcU>Lhh(z?V`I{=eyc~you7K2!BaUvmGUv#zCgJr?maqV$dqpm|4V`YID zwBrLg8!?6(qLpW-&=+S%JYda@_MFoYf{>0A?tfGNXO5Q;3yA9}h;C~c_aZCfzZ0an z{me*BgsO1Ef4jP*j`&@9aSY$>rAIh@Ft1h$uQ|+iri)6!>D6oGgmOx?B5w>p+xNv` z-r5>14Z}?{(8^@PD5kW^*Dd+|fv5*yhNHWsRwvNEEfK@rDHXQu+57MHy0 zqW>HRGRv(SVun36xN-V5OC%DA@q@bcRND88??#2z%_njMRvnqLI1U6LO8za~pXp4R zKyDulSdTKXDP1Tmj7nRYms_?JAY0eeCcVQ%dKaj=^bJYO7cf+l2?3IE?@ac&tK?db zNMi;=7J>dcuo>2gs@Iep_+!-4m-%h(5{s8i1FSFCzS8$S8F&? zC$tbz7f(6(6ir+a9D)|@u0LRT4xMCE?;RhHPget`fZ-Js)H6Fe{}dl`C;F*c-hq+#@ERR7eGnu;zWqPQr+?F@Q(Zn#fu6O_gGebu5 zGa|6YT+`VDsxJ-$Cf;BQ<#=_Mw@URP!9Yk#t-#P13b{MP9hWtvYkcuJ?h&NZFTGY7 zNi`4zuWxP!OAL2LvjI?4MWGSUsN4}5=4#d1W{)r4-vG6KxNT7{kZ7%ZF$vbIl6~Hp zjr#GVLSnwGw8K8n0yB6bI!#?*(B6SAJj@YdgS$*o8t4cCF}}pcrd*PI?p9#`OZNux zARhygC`zg#C<$8t(wRgb@9!EdGE~f|c^8DX{v(!=k@5AA;N`${c~?T(2Qq9Cc(`vU z@6*KOon|g4YhazM&~$bYKSf}ck{2F&+=)+@qWSyV)9Y7%6MOTBcF9TtA8nD<-*;9F zi&Oronq2nmy9riiw-u4GFVvdNTK9tm2I~35e@ub$n{ZG{p-zo_YwwEv#nv7q|8uj{t81Cfk8F{ z;1aElhKr9f3i{3Nh?^AQ_Ue#Es|c1zWY*Y1wG$UUVs*YHav;Az|HI7rvswnhy6Wn< zFJIO<9_jbWF-(`eduwX?k5MBm1q!y z;7I~s`?7HDh~ilHLuP+>%<8A&U)cyNf=-h+fWjvbqX)Tkq=q(?;?xxWO;|>7I3iGu z`eyt~W=F2of?(9vddKy-+dn-%oVKKo!3Fcv)6yVhA{xl9Cbz4o-Sg z+bbkz8;7?Xe)OZO^^o0h)nCUx8z znZ8O=Vyf0pH*7&Z@YP9Al$7z31Bg+$s+d1M9-gpdM1~?iX~ejgK>?xp)N~7(%swck z9+@~QRzDuL(+iS<(LBj8JzKVJl>4{$`K*CLR&9WS2D~OTigiz}uHqQ0z4aO#`~m`y zkGkstZxl_<>7f*XY*8GbSN;IF7Xo)dDD$;D^$k6MFU{v>`*0iN`p5?c%i_66O6sms zaH=$K4KEe~$m1k~?AP*L%m47^xXdRt!JC~i(x7Jz?>X)Cq?xu8Po>%0>%Q#(RmIdh!lC$;Lj1g6Q`k^gOaT5}re@2cV7p2Kh6 zJm?I8H8L&D6Hu2*S@Mypuz3QOC%}gp?el#_Cm>v9(M9nd(G}3?o-+dFe$MUxF88xU z`K{Fix!|_HQobxgO$VCi2*?1d9`xTyfFUtlj2|W7pb@f_WFoapSu$D;nrFov;DNV!)k2 zOCuDBBdIkn_0l@Pq5!xPL~2e%6CJ3D{@3WfAhD)j)-<;Q9x==Y&AO_plsHuL3Ogt| zn^d0?OA(?jT{3cr0agGXjq%jG(5c2;nEnY#S;)pk@ZtmeCOS+8{Qv#WF(lSV;F@fJ zw;b|{C-e66bm_|Z&X`@d4{0()u>SRbwdM~ow&^Fu&|v_k^1toKXR%=^q!$(+gtBN5 z`V)-;114jzD+?l$L?WiLq0)k4uh3=H#_j^lD~w1w6dDnKrFxO}{SnRz8l4EW4^t7F z@Z9GA31*;w2UI0Cqu53+fEFr?u3eTeQ1`TLx53%m-D-mbTs#niyZ2M=0{yRvGCM11 zFd<>$WV(otko&FO!K_+eQK_z#l~uB!?K%Q4>(Dbf?dD(o32tdP)L7joGPnm9GLEBA z5^HK%QDgb(qqoBN%@3a>Suy@3^SWwiBB~fNSKOqn4BRBnCtY}AqN2b8h>MN&zww5G zUTy^x+y3C^Mz_bm?0i^&RTlS!lM-5}5RfzgafY;x7dzta+HR}aR09GX3fQrKEj9!s z1kCjG`35aQPJnlT$T4EdW zR`O^wzR@5thb4P&DSDmMznV)dufReZ6R3{$+d$#>)(VtR=54Ro(q}h*&58P=RB=2Hmv!}c`qXz!w?_#&@|pa$OLz=?yg9R zAJ+m9Bw0_UPBiwM4C-3ueM#_19$WH$A$!|q$1MD2z5I?-C`8s3(d$1$yfLcrWcwv& z*p4|4S%gcz%y#@(vVcK37$*!nsKbo43<&Z0?RSqq%YtQ{2wsWV9=&{}?P`I4&GhxK z;7jBi$lK2i`c}%S52r9#VRE6Lg&4|)6O&7LHo|7Kd?Q~Qs<`wPvFG7B8vP?xV+68> zTJ%T%7u5BL79P_F2Ee%EOJTUuYhD6(0tB#3C5JUW++DVR85)&a(XKkI-(z;;BpA^& zVOpk$%2Bn}gd7vbru0JJH91;n5*;x-U6ZY5e2cj73??YAgSx+r;+j#o>#VvLJYi^Q zENh_L!5MXID9;L<_xo3|@jQHb!|l2OVN?~sMdC~KUu$-z7OASpmGAQ)R z4D;`2m!iiWS^{LN=aIt58#Jsm&3PDHMf55ewp>t)KYWxvj}q>FeSmc}8tiMuy>YfE z@Odf(nDCZU*VPJ@wRC*!$1OAr`^J)SVyw%S9gMf{4y=;Cp#4{TV}wKLfJ^7KJe0&f z8u*Ue+o2~L^9sJ-h`AmY%|jQ72ZT#OtH+7*cXj9Z}@{2RxTrX|>!H zQ4$tSL*sa(TjpO3Qew_egUv&7y2L=VaDGNHEl=n+PqoJlZv!s1V@TD^UjLsbqvC%Z zqm$4Cx=ul>r5zc>-@Xnpe&%E_Q@pWURG8X)%|GLDbTyM_L$s9}JZBHxH062~(w3C< zHof64r{#1;27UCv|H56|c&n8&g05h?i5tbCMz)gYPxcFzjNH0&1v z%W487m%oR1ZTe8<`qS556Fp6*xcRlw)ahG`rpKYxfz%*&mzxiww-#!s{Ks47wmJ_V zJaKPt@-E>sNKyzNmVJBp7c~xFV$GUVCwVXq{v$g1|9On55YO#mnbEJj$aADZlp)w7 zcTcIw0GSH1rW(qE;XVtyWg{IV?T6Uwj^c5y+|gVTNoN-`P9a#mtM23EOyxWWivKwr z02yI$t0m2B1jSo@x09~Dsf8=E?!-V3(9bmbA|RvEPE1OrqCh~AgfInmpawACc9Oi+ z^RV;?UT-1N#>X1JBR>CJGPvTc_IR4pn-y43b z2%aV4xTK&JkYLBN?t zcQxepQxD*0ovuse{e`m(i!TOV;epC4gkm_PR`-@8oes~qlN^`){eI^Bdgp@4_p3UN zA-Fs$ODB}+{l73kl-Il;-vj;oBRXKWv_t^a_4ow0_5(WR;?>JHo z1!Qc%Ask;Hikb+Bg>s*5>@c}t54P2GrNR4r=>eQpi@K~o0m9tn!5m=9NjkEgI9jaN&<=y8VN}5owE8&{s$v-R3Smw> zc=~OogecfTiBNxRC-Z2f(_{e#e?M~>q8iz^PwziLx)(p1z7}pgL2v$!JN>^z6)<*nRX_NUNz7KBzPNV3`6dJ zY;>GIX*WK(8jrn`4Q2p%iZMd{e40y6Nay~u?vJ}Rg zT3*e6adwfNl>29aev_t{hbfMz@dW)Ff?kjx5DGTewuUxYNOtDN@}LpzPxVf>>R4@| z!fVf~NBgTq3Q-~cFhdovSx7mP2`qq|alMj#YoyJ}A0j^B1?pRa>bicBx0t#_l= zfeocm4_%^$s(l%b25qx~-64L8vaF_V>Q@y?1*%%WR)@Kcgp`JH`IncMudc2Fo(}hJ zcO@?C>QEJLjEu+#_49u414Wo3rbfi%aFsA6LWLs7I9idUxF7@&gnmHt%KbWvQqd-*$c;$S!g+VtpQGl*z1%W^^Bo?*IqAv<3`L<;q^UF0 z2~9~Yz=0V~q_ljtzq%$SPDRQNZ(HuN==)Y;f@8L9rfFO9_L<4k!YV%a;E8f7R96uM zq35*OB1ZRes`m>PBtPPN;973_x$nl`qwtL;qPhjO^H1E zo(-aaDE5p5h-=l)wQsXH|GkfpU^};qpei_O&$;!#uTwkAW zq+85^5cULQ$El2Ftb4D6HIXO%2-1n;{rNR$wut0=17)QAs?)_4Sd9?$_V}~1d71-% zubMPXXYM=TFtgmGS_ykqm5_8_sbCS{Ovj6i$a(#JW5#Mw!RP!kvyumI`rv9pOKR{V zHFqK|n%}E?Y!y%Cabb_IwVt|82W3Xz@5i$LJ<7ZmV2bnpBfyTO5gmS2nQMe&VZRV> zg3+_NMLib0dOACeF^?a9vt_jL(uLr?P7e~o4X-)9wP!!<8MmnqK~PpqzgLrwn?p@3 zS)3T-&3crAw~?_BcN;2dF}NE`rmqVVZgn5K!ye}V4`7+1hCw~eAnr|4f|!|^{olnz zN3t{A+zLp&*Nd{4{iLYeo3UOP9UVPCubYRIgpxxBzkZXHl_cuy?5*w=iJT#Bil-fy z|06?lmW-+CM6Gj$D~0*?FWt+*+>d{GRPrO+UnS((C_ECL6)Nb?@%64b%#eKg$!VhU zxix!#f4(tzzj1RU(&VTk>h>2D1hbux?$f{&kR`n5;|pfy4nm_$4j)s^uv&~ebSBSMT|$po$T?v?p5!28|o!XY)5Pf!X=17 zW>xpr_jPTPiOvfzppF7o9&6j_is_r2`>$o~qi$UR$O-#vhNp;K)&yvb!XbO)tmxe; zvH-+2w`%^5#EZ0ypnrLZs|JAR0lMnBG8d4Q{w}<%5;;u(|R;91c)mJTfh*NZ5 z6hKV{?cX9jt?fszldHq^A#=S+v7pPsqDv@6stiq1Qt=w00#%cvmh9O5qJb#6j=qh1 zDz-}F;zLsiydRFH?Rj%swLC%};65Te&}*%2@5shuVWJq}(Ga*k+18&v*KUFF1@r5kOMVt)L@7hJ7RRYKYuO*^mG-+^fBI3}BM3xV(vRD3n=Ts$9jkd-4E zq!2a>TeS9JT=nnl&xI1K<-kt@b8R3ztAy&@G6CLj9wSY-gHRD>>M_Bn)X5I;HI zs~)#m??il(?Woaz>SM9GWUEOvmH4aVEduLx7^k=sy?p22i;9{M$bk@jx3Z-mgV405 zMGBLXw*w{WO9xi>9%hq|KfbT@e#}%Zq0SPg$$3|g9)-lGGej9Yjjw&w;9ey(r@I$* z;SDvlThTnZrL*f^<9S>!qlnqpp++{j)843xFsyY}hqFb&UIk*$kBk5zDoB^MVF+G5^jC<1!soj2M` ze&Yl-#gik;+Dn85T*rn*AW+rqAwclaesGw)wY4e|3E1B-Dq8ty9@40|Hy2dppO(k9 z{#Q{6+SIL?XVGJW6y#Y(1vgHC@tPy-!#oFf<>hqgUANzp{k_`AGCa*+La*7%7qe)? zSI{)53I$S{8EK2wym>WYVAH3?qR4mUz7gUrf4+nL&v(!8BL!6xQnvmChVVbt5?Lt^ z`4a%0EDHYgV8Fkx4!y!bUub!PqRV`(=cm=Hj$oj`L>{Rc z5e4c@R7^v_(u4c&a)`}2EZV@WpN91z3S}yvN{%l+R!SeFBh@ zR5l~hdQW0JhoxF;Nl#+)w5E^2?ikqkrBrkcKW0%Qes4NXKxqf2Zo$O_0CAwio92}S ztaqN%$$e|tMGE(=H0`|xnTdF~&sC17sfh6h;~hH-8b7B}upfae^N7Gwf%T*Yam5n% z=cC8#3W5o|KjlvtplNGepAz*3GE+bq`Rc723@o9ZOYdF$A8O$9{ax5)YNbunE?>CtOrviGXU(p0b3BF0=&VajHyUv zOl9RZE9bQdNy8o~I=WKzGXs_>au7ABniN%lFzO0Wy!gUBhk*43w9l+|m6_UIDUPSV z>M)vKW??&b|B5Fc7G$NiGTplYKz5!TQH+83;CXjyo`KWGA&GRZT}2vCmj8#r7KejwQg*99edlBkG{PNd}#9j(Tr6ipe(U*i-QvnmP zq6iVz#!!C@{?d28-hQ~@F~^rv>+a;yP}(@f{Gn|8(pJ9vYda5?bVe(XmDTp$0hbp6 z>*-quYWKPzCSarb`_c*B!Srh`Kt(qDHuDxXehXj=vj+dnXe7jMD|lG~g;<)zc2hM{ ze|%-gmhXJF50n(C28u6+xn3Vle`?3ZyRQc9Noq6iQA>^0cn7t+6K5D1+nF$`1E2AM zHEY^d&ujYSh6}vnPHL}_RB72eZqAD$m2XU;KY0W(yK`f^F&Bm6PTiemPdYhi5jUE6 z{O(u!p)d_G4H*E;1Xx9Xfn`m9qevPYq~WZXC`d^8D!HMF67>MM{dZv?dyEvQROIBq z+R}9v8smk9G%V8C7IZTk_U0^qpk`$_S)nO)SdVJA%sfqMnNYCxInWbj(*0jo9@A*ZL|M!c~?Bj-5m%z1(`aL*Bjftc70{U@RKozSNyfA(}Ko&%(*LJF|;9wY{^V zs#^)rqiA5`@?jg|qa&-G zBnF0@=6~yf#h=2gRKe$A_`uHTAl)qGrgJZZ6tg{p$MDnVB7Hue{c^ut&W~`7~aP z-QW2xJReh#2q;%s(N$QOpXdUgd>S;|w$*^uVYV^)y!#Ra`{xEoQWhCVjZ$k&e5f2W0AmV;AMj@BV780c-+q;`b zsZ;QCRH0Ni-#oEgpSGrv@A2rx8+*zhXhl;Mf)eL_%4LgI0)6Te8`umCGFn8x(m zT@V}QMo*N=^0{CBZ5dd#zeT>egmhX0Nxz;kFG17JkyH(2U&5(Dv2yPuBv-GDTkcMW zg2@dGaIu*W&$-df+~zm4`f*W#jS=rAckY#4`q9t{QdZ}xU8rgO+WD{57F4=@`c`~$ z6r7xeS-YIVD0bL#+IQ-DZc2$=)&+++V1bj~f;g+|4$>+OIhSB=j;E$#(iP*a4q2@% z=>xN1-BD?%k6%lN9DpBat~x^k31xQFh)zpOWsz{eXQ@P5oC??!HM&Y)zsvt=c=+^< zVD0Y8T2ly(&j%c6gKulO>9btgW$K1KH}Cg-1}~QA0H2eZ*C^=A#vc|!+m`fx^Ahxp z$jwl6bMOgtwQ=&erj?qoHD5D48!)++4aSqqy(7uu<9~E( z@c8hSUWELK(}`O~e^*}?RZ3s_dkw|q>I_H=(#n0ev=%4&SIlvhA1@!;`?@FHX-%c} z?qG^#uc`R8OP*>OQO4qM8oS}|CH5pemK2}J97%t`X(n%^u3+Xw%eiLdZ$z;FzDQOm z7P!Xm<*?q>ZP}GdNXr`oft+q(!2IwXb+$xz+vEC%%r8H)w#);BJ_|^)3;K31yCHt& zkQ_X@-m$nBl;*UbT|K~AFxw~3o@S&)?lXHO%N{eXJLFToZmvSJJFk)#DN%*j3;`x+ zcIG6=lHk1trXN|yoJpQMa&pbri6XC~*2V_wyrMP9!o#c`+* z0$WFG02AAhWBC3~zm)@}JHF%yU*8&*$)g?$ysOzIrY(re(+v10N#0NcN z4Z$xjL<4V56o?L)9h#Taaw!Fp3Ngjhl)W}0qCBXt_uBr^Tdtkby`Qc>d#Y%i8=aoq zYIp0QNK;wI?F=jAdNsG2DNkKkhWA?5(?I>(V^#CR3;uNP#;+oXZ{kj?Q)nU}FKTg| zBzg@U1Rl3p8fqa~nXwn72rc4OZ_`vDXfvqb8CCu3TNtDlwCjLZ(tLZ8(B@hFVP=%H zjvW)UUFVYioVg+lb=0Ocl_l1|jvP-N9N`ZB{Omn^jQ#Ke$$`*(4+=Ht!TYHtq^o4=_!^J^OOkHwmZ}B{P zrr9&OWBxXe?DxH`_rLY{Boij}?)hj&p(s99-`a*XT&{>Xl%y^YkiF?UJ=FWQF7&&B zLQsu>A*M4quWAmwfo8-2S++hr?QYs)Q?hjv|Y3C zaI%^dSRVI-c@=p2=8=h{bhCV+hqbj*tJgjS*emc&mjmo8XWgR=X5b-Az67qLKErkj z=ZZ?lO23ODms-vnL9IF(Y*-exkHC+olrNeMwBDsZv(fJD>+7L^E23={;qv~yRsf~H z|1IL{xbbFm=R0fzdJn@eic8zZ`1|ce5QCk`A9sy4kj3D)n&n{px48+L zF0R>dZz|I4szf5F|p72O{&6}bq&y~LRW6 zfxRuIrZ{rx+|sM$X|Fe{Y@;f9=CImz)(>i=9(^!_le2JEQ*meQ_D0hL2FI;8RK+L{_BsY?BPY3Y!sg8}-u}f3k-NhXUfFKPAjJaAn*&wwy}n6aKmmC`0Y<8m$-7&jM%|Aq2SCE;!VcHr!yf4Gn7qb*cFCNMw z_`7Vb^x;9)6Q>$|FGU1xY5`T6n*6O716Nqjz@mD_xp#vcb_dUM`bwx~QY+fx!#)$B z+xzVuFuvtD0!7y^r^kFy_OA;ek^(iz{(bKb56Hf7dkRS^BS zW0Atn|FCG_lexsAJoF6Cu!_^mi5^ZG>(_2-$K?W^_3u|D_j$x)nXu%j0VG}r~v#qq+w zP;s#f`T1F6drorI3B(|Gcx2bUFC@M`!;kG^#kBwpL~5qj;f1QrwMW{g+z8-Yq=#y;_+ge)|R;&Z^YO?TqF6WxCOo@|cpS#@5yAkJ9j0?>&Fg zF7|ylFmdzvCHwy$-`zNxvfi1jFY_^8%-+j>`JNHUq%AQ|$w8y7ea<0=Mdy&lN(npk zqR>AXlZ--veWe@hY=74-osgXCEz zI_b3k<_2&9cPjFN2_TIALa@K3;%HMYuG;y}_b``g?jI1F@?|k)GOYE-NwI#c+x+wz z{(rXikHZPZHxMR8T~lJ|`%EU#M-B`$DEgiW{lN_~W*Tan#Gi>AYasTJu~U?N0zJ9K zWqc?r^;;s&L%Kf>Az+$X1yq+L8rkYh$!|?qu9Nznl{?RD#Dw*o((MeCs}^I>bb)p` z=b|JV^!TzMVRJr&nv(Jk;I@~X24VOgR*rk*=djGncEM1`#*$yR2MT}SORL|>&N@sc zBL^q=NBmron<^BDQ;N@7vVZ+t^pe)z^!%W!*3m`FT@JG_#Qy&N-GG4UN@XKgG_ELM zOF4S^6@E6sVf^x4pQi35r)b^r;l1L*fjha%U9m&Q@onLp9y?q3I$C`XiHa3^h0uc` z!>_Dm1q2A4${W!_8#IAXX?1+z+b{NinYQFZ>I)vyVg1#PCet01rYv!0PqI$gQqJP1 z7TB<+kN(fycQ1l2vYTk`e^Zorhkn_J{VG3wu^&$n+1d+ysc9BNVJH4xQOX;TU1*U7 zEf(mEy$JAzmj~J_mn)vn{%1H``*FFPYi43Wt6A$%Xtcn0|+k zkII4?-Y!ilNwGYzy8nep`q1KpkU6+p6h2olbN=#CC-$Nb)am3Lg?eTu;?$Kse4p~hxE zI@h7|46~)3Jr72Hc!7uX4t7#)61Lxkk!U#UjqlHCVMY1dih5PV@>or4HC5`g1E~`(2NGt?!$eB0Le-J{u z+Ez;+s_1|=w&5~XG(?b6#zblKdaxvd@&)0tw7W8rvd52(9gW=H(-77`R)LX=K79>v zm#7qwu{w1whax}O*%h=-_(qxoUovmLS6o(M0!C(MjB4Z3R& z2=lZg@H~Gh;M*MBRb;@vid9&xN+fyrK=P+&@kfV+I>c-U_Eo>S zNCBsh%_92z(=PvBGq4=zDvAA0{%;nK#M!!Ku}Ky^QSm0_G@=@rWq-H@u`7^y3OnQr z&gM_vyOEEXpiwSW`}(%5*lyt0AVxTvky^-79BnuzroX>`IbpS=UhyZO!a*-aOzB4H z^M`BVz(pwV{k7r8~(@K_Rky*A_H~L!R7&+?d={%RMrF@O5p7 zs$B6pjKY@3J0f7>{&(h(Opv%0vujPsf{&hU7m!?|7sgifhaga!+bnY{x$Y+`EGJmD zrmA5+`CGH0jv-t6nSIrB&WAO8%GQr7sWsTrY-kn&lskt~l`_>|)z(h6O1Rw6x-yLE zF?o1v{9Sm2R!|pgqQGxARvl0bf20no{0s&&hL_+?c<}+G()QfFZ|?%|9b?My5d-l} zB~RG1;L$Mt>!j-Tttk)x-&HX+EXpM$+cMOZy&)s$ix#S9aZfZi_ZHsv7&$RUEg^1hoJx~jKCUyqr=^vA@#XaI7tE;bnqy_;?`}H39VS4! zRMa>;x$}z$W3?t*zph*jp_acYHX!%$&7B*B(6k9clM<46#4|nmDH67*5|Hu#bKhyB z#~>E1)qsG0oCZg^LA;CDOII!Jh+L77kj8cUt)!m(JEK@GH+s8cz?gI92m#+)VT3g0 z&2J|t1|@mE{63jm)Is7hDDype-72-`PxJia1}+x=6#3N7$6;~dQ@d`1O%27#mRuM@5&eg2Ex`H zepd8TlW3zA{V+H^B#YHOWiLYt3>Z;7<|8rQ-(54vPGozM1@Iw-dujF(Atpe8q61OU{S|U^vXkOok(8p6^ z)=tMbAiVZAs@3uLQivXF6UX*bic+Ska|r6Pqjl_#mvR+D8rYu>Ln{T{qj3T4%3aaA z8WwgmQU1Rd*{JJn4I5CnWcz*;93`TIEa{E^id%XM`ijW<_!-;T;ZpjxSmfvcH|}4C(Fxx>baCH2konpd%!q>MG|6ax#-`* zK~5c1S6y!TlrG*bY&IOK^31cCTx{=0Xue!o(p!T!dPpxA{r95ThHlfCfVjQwK7pdw zu!KFg;+XE=uV@orxc0iYqdq_$yJo&^eX{w)hW8aWd(|DvR3@lTh6YkQX|E=vEspXQ zAB2%mBIlHYbEz*I|CECh=6WBZC@-G(qmta=Q*_d}onM$qo`CcF!*5~2P^@%h9~DE( zAiPf`v^)Y5@N81LwMADPIPbgx?=P2;w-Wb}h|){A7S!S}2#JW0n!sHyQ7~_+S*m9U z+7o7US?MQ#UEm4Fi+i{OaEt>F4tTYm?!Zu+H50+)KGm;*%W;SWal^-zKfhe6Y*-0r z;kWpf(Qym4*gMV@85ARw43uAZ1?#rHwHjtL4k;4FH3_cP#P8kRbupvfp;n?Fc@C#W zAlyhr5yhh3@zl#P@ALyT53s7_?m=Wj!L9+Ke)3)C6SepPD>m~tw_p6}Sv%eL$wAL} zh3{pJx^@hA<@h-uEV!#;suo&`;?#h<$*Vq!{bM^wo0A|s7Z^7EpT+t zd1%3M^N3C0e$r;~5ys?Hw3(0)oy0d%d<~uj#TZ7z8 z+?qJTL8(t-qH~>3x#WWeRs2NHeX?2;WZ~61`l5YIGmBJ9Q%j_|LP)B)KC&FSYTDuY z5T8ClG8mEOQTRF*K8oAhc*KqdIr5_N=a3Vp$z}cGudexn4B~Ov-DEJW(1Ms~W{~(I zEKzV~m7_fkwnoiv&;c9Iqj&ePmif8Qh94_QaF$1v~YQ{ zy#XU|<()MlG4x#b{355IHp`rvCf)P;dQSnMm$YpRe)r%8P*K_Kyx+nANo@q|3O%u+ z!vEO~z`SY1CE;bp2{nCBwNS{V=mGz7yew{;Y@e-%D}#!H@O^vD3H1@1n>Yqt1Q?W$ z6+>*gOYLr68|lhV+^rO?F01S{2V5ySrXnzf12T%LGn13O>WyTh}kn_ztCAc(Pj^R(&_vS&Oy^Ftj^u(BBR-L2A$3@YT;iAaRf(|6~C?-o(@RQ@@E+Z?}<9DnyGT%xcev~e%KfJwI zwH8|X4ntO3VSlP*lKEo9vQ+AWG|ReaJ4z4b$K;d0B(L6jH4$-A=wI}_3AU*J$J0x>4^ zJBN;JwN>&M_1yu(SCTQvZu5)gRIL98*CbS24T}sWO~UfEOLL3z_re1QwlaL4i~}OG zB-x>x%~Q*Ux&)i@lD@h_pXV1vUzFImW39@0xPP7GFnMc;+{ZN2v^!;Q{8bR=eo zJ|X&&*nv%i!z9=lddP*n@^j%LUYkdZ^4<#Kg*sM84ix#sGABvsG+Ez=fAHxu&X{sW{(%; z#Ar7PmX?GWzr_SZu(AW3C&d#w6c4G@N7XQgFcYZ7S3V;GY`JTwJF!)Ruc2QNDKg>K zrtp@&08={bh)+1%&TY`Y_0&H?FaW1X4JQ0VVn_HIk36!91@zHF<)^*vdS0~F6k(Uj z=8)WbJaGJ+lJgb&oB)18knddA3DG)sYG}UnJRy!{k5}{s13Xm51(P+ZOJM9qi>Vd7 zDf}o*6?!@EmrzH5j^g{-A_u^;>vwqLq@&>rp$&EiHkq@qMn8L_aJ25F5gI6#+}@uP z1|2snTIbxr{6K*KEQ=00DF-)o=R4}KnaoAi*fSXdqH^-;(l_^`aZsv0xtiOLJZW0n z1^p(L!x4AKaHGZ6;|kkEOTG)l=s+9N%yIg+fAz;0>YJPIwbk7Gyj5MWX>!%)eSVM2 z^e5|^3Ue%r61B_>7j_$MPO`(Pa)o$=o~dhtgtJ5AmERT&^p-VOo@>8zId!v+kVRWx z#tywDQ)#QDip0O^h~J12$bJ)3@bx_7BptH2eOAdac&+G`uHyDXaq*Cu9&0<_52CcE zM^prnWtXm>k50_j4V*q1N`XjS(qolc^?hVv*#cCS#Mb{h3Zb3jcc=Lqq6Z0@?C4LC zxTxSQHKH~_Qk$?ge$#0VGme^@ZUh@tCcU*x(R^g>`k%f0?H#AK#jfCQs}JDEgy9Oy zxF}0=6xJvl%hN;S71f0#;^pql}6WS9meu%h%cGL!*6PoEsNRoh+k>4P|JPbS zaczN?G%yEBSx}xXt$b5&@vFRk55{!Ax|1e8uM}nG*5c~B0fGwV!lEdww{i=}F9Rn- zM$qj_4+FWwOKf0j6_&uCSo!+?I$NLOIHV)=zVeIfjin$@m=0oQcJ$l~NJ8NcQv)Cv zt4zmita;=T7vqbOkdca(f|@P+yN;vZq7OdXnmou1Iba>8jGp%FJ!*~#I-!62!yxcg zC9(xrWTUIdbjA4>5#bI!h6L7C7g0gY^9a@femn8{ubZH{%mVkiy|-bk^2D-k_lqYP#BsPyW@< zaJSX+`iCsLSl7_Ro?Kuyj?1`;+#6-w#~#Xl*e-VXWEEB@g=eZY3CL$=LSz=U!yCfW zAA^|s07Y-IICsmc#OJ`+8UL3!h@h*qo z$2SBbEW2rWKc{qSjFNzCk}fI=Le4oeMoH~yMWh46m<5JVKv$~ylMWExk&^M6+}&Mw zQ+Nr^p5D?&Rmzu<)q`Pqr=hGwMKgT&MLu*qC`mVctf!jKu*TX&VV~vLyv$;M1wFoU zLe+KjRjhar&0?&3}(*bn|}_yKKyMMy=sup zyY0PolOx}^+grg>{0|=MxHm}SER5~P8#5<<$@>@#qCLL*`c0s$3Yf4HNbH=|T!q+F zyDwO^`tqqV?Ris!6oH$GzjV>DEg-7pU|J% z==}ZyPfb|-3urU*K2yA{fWWP^Ld|p#7UmL{YSu->YjpC<-IiYRbbezmLzmVdEw%O; z81mhI;?}**Md1UX{nmo<;N|HJe6;`Bkn#oy(1IcmB=oMqOs&gDm+Sa~9fib5mjveS+p-}le+ zFktxJ#YG1?xJf|Gh4U1j+LI4>HS)V@tH%YChd@&=={ek(c*gN(X?~C35HpHg4vzIz zm?|1hPKdSF(m56o#C<|usxVfju>bJL#f*FPFeYe?R-#F36Q#($G-11R*U?#tE%P1n zs5u|QI##=0@L|J-Q*aI)9Z7m{fWCHQ!tm_LnUJlgqI#^sx>nMRdbBf|p>?>j z_24hyua-LqsU3>#!Kps8z5OR0~hSJp*oo=d`&3i z^-hQ?Or?%m!5@cptxh$~##wrq#BXJK;?&)SFr?zrs~Q5z@~FM^((T5Zm;s@1mD+8y zuu3!rxaLj0R2`5_2m|A*{>bO?;a;OD^Wd9G#)vK{^6aB8#G&BH5Qnc*UtSKVJ?WpQ zov2t7_kJ}&5Zgl-*ZJmgilvzqh&xgH&W9n`dy0z9Hyb@$foP2;X4Mp2XTk_d{mx(_ zclwy;C6^ff$CCiXC=4E6L2aXuE|U3fUCZWNS}J5aEPNe!vq5$9i=~fFG&~A<_$vCo zQogrKIfB|rnG6t&9qdXT&z-Px`+o15C>R<#OAh=tgH6@8=%p z1hd~}@woHtf#gxkvXlPcjRC(&0c~VR$3w{Tl_pJ>m43OsdDHqFAw`n`qOpS&A>UFN zGNFg75OAsPt_!Cr*+04$^nH*=kUL)%YO8(^hI9dzJi1F1X65#63e)Blj(y$UpUP}= zA3R`p$DR^YTCkx(oILz;Lh$uy6g|HC?HPD%M-!9s$w5^Ijp07wof5nu_g2U!c2ArJ zm%;sN%CFC@{Cr*#r3zDcOeyE#F*G%}$rtM{@&YrR+{^alhDa~3n@Jo zVL!Qari6zk+XP^tgs!XqUg#;Kgy`AyFA_nx`Y7{J@B(wLdq~6gAC_xX?}YKmGcYQ^ zyHngBekG{LJjQ&Vv-Bqd9eUSEeGuG!_+w;S==>M8n==hgQJ4>yACKpW2t6nkVt50B zrsB`@81*ZGONNQj;K}7Vp5;>hx`D#V+)o+bQH;T-RIXm_`IM`%>ue&N(5P%-w@SDT zd%B8cd(Z;!RpI2{zqb9NuPIi~%%X22qfv4pRJS^8`AISImx5yRF`2`zZ8y(}JiFcm z@aPiEyhM0@C8ev3_?=zJ_WzHas;4{(n0EGSf2~?>SRIjU!zk)%+?kx!oX!*n+(u}8 zfeKS^IUj9!dQQmo#r%%O?U2nL>)9IdrES|ns6+%nt$uP=(LLc26yILe7rqi^M13`)AIjpDk`RVOH%G4wBm z7KDQ6rt=2ohlF83UHXp)`_HGQs55Y!7!HN{$ax1PF^NLCpC}n%3{mTdd)m2ta|Ib1 zrEsd>br*Pk!r45!hyY0TFHpk-AJcU$A`CLjBWx$byib?cK%$t!f0sN1q~<_iqoN4r zaRB-d9nD)GHQ|W>32PUo2VNgfHEy$f-VowK)kkClk0(w%2Ohh$-vM>1ng4f`PESeWrnURox=rE6!WX|7=|nT|h+s0t^k+4!s!2ug}tT zN_t_w#tERySOE0R2wJmOb5Fa@HyFTtRuPRvEkX2K+6%^wUc5WHlHs|W2YFrr=Z)}X z4oN-djUxYixh5m0fvnnm7AzX4{8DIZ%fxZpwYDq(CZm($*Sn*)P0{8yKbZd;w@~Om zg2073W|>Z=!z^SGf3OdQw!%roge9Y9E0fC zAsy2FXG-+`U<+$Fr(0P5yM;>GJw_eX6%q?gWavxg#05$_|6lBAyxOV23c<9b-b-_md!@ zQV6aQ>{OkDl34UlzySIpLJbHC8Uanu+oh;nJrtue+Up!4vZXQUpl@@FD0%j=mICWl z+;$r=Dw1w>NgFianZ@R?N`AtS(tjAZF=Im!dq|7vLuBP#=tQHM1PiFGYBI&~A!p2_4tKfK&}B!psl$ zPhe6~KiF;+&=!M|U}(S_y-GHO9PU$-xjQOQn!vCVVe%Gp3n;+^#K5RmT!$__GjAs~RV}ci2OJ#|P#F?{%?uudGGW$qUR$~1; zbJihmx{Jk#NP1oV1MTu~()pFj=8NfY-Oj62-g@YW14Eu-quKse0nxA3+B5_QFMg4uB>ECG!fFc}gWb z)gP_E922Ll8Z%OCN?ho>w?3OlCt?2~elPCf#!CVY0Pya~C3F1!II{<7i^yg4K#BN< zSOR?Xk(Aup_$RSE-|{tqGDH|AK#+6m+rpppd-DQHgf)Q%{n;Yrh1rM7Q}cOIxt3gJ z`nC%8RX}KRUL_`IVt}8AIRJEV^6wl6OzA z=kl~1s6*$lm4W{_Bvsmtf=~zJ5u9c*Yri1%?53K*^630gugwtGC*=_3@fC;un83ks zgy@n*5C@qk)w_&3KHme(y`lDivkuHy0knIFqQ~m9yV)cm-j5j0lyYUc6*`OI|5;IM{ToRhux{s&WKGzj z1;v0=+gb|NZoZvlo33X34fv3R3+Sgz1Z*J{14d5dK*=sI(%Ol~P+xy+H=-;dVULb{ zC`3G|M8u?tJBbVZk=V?s7r8Dkm?7$mX)Rzm3 z9W2ZwZ&;+JXEcWeD_!mCN4XUR1gcs5;JfegUR|L-g7GU>bGC9g(QY~lCuTF@tlqCW zcB4WVi{v>?CnsO&2R$%^Uq6eXa_T@;CD2GJft)~%ur6#ZibW>{qu7}OorqW?(4IFE zN;l$gYeed?+ND5#Nw3Qg_;oA=eOy%g9VB5SP=#L(!R^Vw<%jin70i(stH6Q{CV=T6DBSsD3AFvcD5}?5r z7K<_{?@?L*BiDE{E`rs2Sd%xuUx=VYs|6ZUEj>z5u;=f!;y_2WCkI=3r%mP9GfT|u zY?M|1}&}?O~z2XZDJY0 zEH8^P%Lw&FMi!b;UIX)(TuQW`)2bNd2u%o6!&u=G^;eI*Ms^va<(uE7t?=B|4m4hT zZtvfTz$Qeq;+Ql?vDM-+w+A9j?0~@Q5nsFLIus*t-{YkYfxP{qXVd0=YY|4p;}?O^ z%bsE-DbQcv`Iz<`;^Xu5k5LdkDR%bO*^X9LGchnU$Olzp!_; zs$96+`xUHu^v*L%Q@mgO4cN}?QetFd4s_VL(T~HC(~k1&IYF&iiBv^qMzQ;hNMOi1 zX1?C!9@2|8*W!1Dl)VI^2B|Q9{YZZ)QBYkaT>6P2giM*1zsw{##p8Vi+C`6RHKwWD z{^OVUU1Cj|ycSw6+qlaQA4cdhD_Ze)DO>^suL{B3aV@v0pUg4Q#gD$i4M@bP$3;!I zkA&C9T7Lxel~Yoye9gDPgg`K&h$UJP=GVGQ92TV1to~Y=c+4WfsqGHJLNSU-u z5V_Sepzv4%^llS^*rXQEH)rUqUrCFk`|PoVOA920n&skHvnRdUU87R884Bi;BC`DK ze4nt0Bwql_gueBiw^b=?RcWM4)!Z&C|ATXhK*=)Q!Op1T)^dQLT8hhYnzUl5j*%5& zNw(bqMEv#=)3fFf1^h$38C9Q>&&GDY;+FHvW0t5{7)TJ3P!r-t>I_`$t2P8qP}XR7 zj1y&4OH8JB>J+}JLZkm-bTM;qv6Ww|Cn-9unU~P^;I(0{EsAE0EyFI0u(Xp6uDM={ zvo^i%@B};2@QMRza}pDi?s3e~#K}0@V~RK~{9GPI6IcnC_}^VG3PvFApCiM5M>t}3 z1%(1=Vb&Gya$)+O?x`E$tS^M`l7F$c3kE|A&Es7OaV1dad%n6Wx`?xip=}k(Y>a>B zlO6m>X;sge|GtSJcE@1g=&BQgW%muNnF?h^D!qfjSydmG@_5huG?wO@FcKXz^-=ui z%tRFY%?$kQxtwllkFR6L4Tpz>M!8QJK%Wyz-aonR*|_@fPBYWbra`v$7171_4t)xd zMh$#qZpouwXv*0Rsvd_V)X=HKzmo46)xYaIDw@_s;zKnhw&zk+F+lh`f{Rp2X1$H` zo8S37s_#R3F(svW5)?dt;I@c^(&87IHJRCPxOovLHQ6ij4zyZBVcyhD!{Ns8qv`0| zgLr%cQCXhys~Z~*TRV%NZ*46Mn>~~jq8v^Ng!e{`oB?JW(5uY$E!z`4>3EJY-A!<% z;NG=ct%0XT0?(?LBLtObNV1UsN>c8KRw z;aK!Qid>e0nbDX~B;_BHAgcG072&elNQ+zeNRcR zIVBaFXoai7l_oU}j+Q`~3*v*-#Q`Uxps;gdxCoUlaY@5ibqp(3<=jwLdVdXtWs?Fi zG7Pqsr@7lY+MZYS9qV8@28P{U1^LR>HvWLw(u!BHdXS_6^9x5u!Cfd+fjN_{&kRrr zpX!F+K+R^P>3rSmb<>*>iIk%k5Sz#r%n}BnDLE#4q0<1al$jOQ6^zT32LhkrJ9#s{ zH~M0~uRv~0^xUB9iCP7a=zrBcPQT?q)~3mN3rDz~ ziy(gWCQ2{%vixDfc{D^4@fn$u2_FUcfq$iX1QPHuGiFhD_O8xI_`%NOY&ha#o)af@ z8z51^*T>^F)%3o8UtbjJ!7#)|eFkQV^s+$aLrx?CrdVOGHz#tAZ>=&qm-H;EiCMsG ztZsRw)qO#ZX+HA`(mFo!G;>T?BI~TI-P}0fWVD?|5G9h7*#|n4K;E99A6J;!^Dr)B zK+wB{taIm|FY0bicHor_ya&Pw@?;skK`~iYw8SEb|Kb^h^$xbc;d9C+hN;Lh5Q}D! z>~R%Gox#o#yc}ph0r;+9xGias`3jo1X(P46O+H5X{2Yw%-VPzkVDGen%X-CXc-1WQ zeexI9u$<2=n4_s@nK-ag{h@;HoC;6@y|k8Ud=~k0;#@y7`YgO{_ttG$(vuZ;J?2M8 zZuwcC&W9i{XQ=>9p3N6%CVtG#HOIL@BKFLPPYRjlj23K^x0-!Y8hG?nr%99t7N?Sx z0@nZRfX3TfyIJqYh6whQsuxHT0rGk`fhuDP<@mI#x4!c#{uGX;oE#uUR`tU(#$E68 zy=$jxw#aDl(F?ePH6~moZ}2pZzC{u{T5~D&;TfLiZ%+1MSYpCo2@BX-2PDrJc{xN(L#ozhtNxQii|KAwAA{InM&b+GluiM> zaHRV1MhHUR$DS_KfBIM>V{1mW)rS_% z*z}n03}bCKu8iMK!+a-ZkPi#irQ?Z^w+X zu0KAjKKvrG5WYJ5sP*!*2W_m6JNGfozLDhB%G_oN{N?8Z7$qNZl+%s2$I$9?}CS-0Vr@R<{^|nTT%>2@*%F?h`fZvaZ^+l$V9QhB=4CtTwF;x1d zb(|5@{Cy;tR*awT5HSaa@p0Ht%$i!g67sxptNKj?b@80$}7|x#Nvc zHUcRkS2nS|&}RLZAAyD}3>W4zBh&5tzp$>){5`LQ87>y}Di%MIH*#(Q{JqgKPs;I< z54G-vYby~henn)u^4b6T0xKzxBr@%2z{kgl)*N8!^1ACV!%W38VgBD+ePS>vOed5H zsn>d~jM1E2v3EnvGTT~t&r7=ZJFgy+P=Vq`^~m*`b~H^`aF8~>Ko^a3*YS|vE~<#} zg08}8g$lKthpDRb>dK*{Ax+_1f4m7x#Di{CYX?fEy#?<)p|CF|Wj1}ehP6*vf(hJR zZr4WMH3?bo;#!Nq++7-}xGX35nPy7rdC+U`|43VyFpPow0kP?#-vjjgrBij$%M`jj zqdJp$3Wgk4j(@_5`=i9_2Lm3e#n>-mZf2FftKngb8h3jyy?W(UPU@%4J+n&4w?<5r-Ib z-~6v{2x9@Q!ze5XyFs#h`fvBVmjQ#8t=rYR?g;*Z8dHwV&nZ%Nnjh|e{ERZukxAn>eg1NadFydt;a*#s(~6*Xx^XHx1M?- zf6t=H(8!TK2SFe7+H?7{3Qs)CR~37&&o2|Q3{yR}llbpSl&rRCj!e`V?5gz=c1j!T z@lsxp19VD+B~s3@o!r45+THWnYUDces#qMx?IQ8A#@m3iDXHD4^i_|7n%x$QhN08-ZNow6+bn#HwMX4jpc%EzdTj6 zJ^J{3aA4#I5~8~Ati65o&2-^GQKJ%_#|g6&>q&z*bAyxH=(t0(Ubvz~$dcdVS6pt| zkI8j857N8(jSt;&f!}y<9V^`8&F(yPlnOyT@IWb?c<3hr9<0y=++0-2gyv|3b zy(d(3l?(~Ieu1uV4gTkDfE$$fgeDe7@H0fAJmh3*L6P&)Va0_e@tMP==;9E@5X!(^ z=n7v5lT#xX0r?i^Wr^%;)ZAP1o0z{^Z^6w@36+7*8{XVK&6nHKqS#ba~ZCz-|Q~epn{7m2Nm)?*bBz zk^@#~$+39xnGVv|Oz)QUUV<{!E$ugQswr6IEEsDk^{bMzG3|*gL}ub<2N@o?cQTeQ z$=q_(m{{shaV;Z)_ib!#z5Dg5UMELR8j!PfV(-@H@uxQB$fyfF^j_Iie@@OD8z0{S zTbwl*6%R|a_lGm9q*0lQ7gR&zUUySdEzi2HaFQ`HT7yY!^2x^Zr0)PEM1|@M(c)hP*cE$(YJS4oV^nqSR83oBocrIoseI|d2h*V zE?m*3F5tG=Qu)+QGX6>Y&cntd&#heY3ryJzm<#b24`$otGJ@Q^#h-BnC%J5oRS}zB zPaeO1Vu+{`cl0&sDqZkbeQJf&$I(9=RPmQKdJBzz)hSv|ru-6geYEzlXK?W)-|4nQ zyd=|?0-#6v;V)WvLqv|6(7=cUo+OLuIn^y{P zn<*K31+J@%$-d?;A6?5!YowGDdYBXW>QoKd9V_p!yn}Tl zN;V~$(HC~~a(wq8MxfLL>T$WhQJMV5e5_tu|Nq+i zs;Ih}U`-@=2*Dvi0>M3K@Zj$5?(VLE;O-vW-JRg>?(PS7olX9G=l(PEG;7V8H4noR ztj*b{yQ;dnzUu1whRmz01Rni~V6BnbQ%^iuhj@d9zr!X}P;A|Fjm13#eT~$t+nnA` zk)_emwYDBcO3)ET)H?|ceUFvaHLogpi{nwNhdq^lo(9&nLd z4}}3qwR5sV3E_{bR$5B;@vYVlizj{x&dPfJTXR>J!-RPw_stFymmzc%vhyG9Ei4{W zpT@+!9bTEfp&a@wp4_&{x|KW&MW-iX*bw#p84*m7a{%v!*vWK-kwmD-+N1}_PfXO&GhYsNC!}%|Wf3h2NyqQD$R>;7lO}@4(rXpZjjI=ePL~T& zQ2W)9M`_|S&*}J`=qnSgn-zvUp#d_9poI5=1Syu(T&Y?D{*@ChYK``#00AoW4nUj+ zF0}L=!zlY6==jR&4m-6C&@CsrjMI;D<0HPAQ_J(B)zb^kRE4@nM#`t##p9d}wNvM2 zbg?z_!P6GH+VfDqd=K5sr!DRt4rbzHJMG5^q7}K9OW};i#z$;$CAf;@atKq1Pz&JZ zE(1_c_Rrb=Zc_72cSx&Y6r>yM61+L5{$U8*JFJ;{H~)vWMy{hUhon=>(f5IQw@Aow z(9=ri37%ac6wmcLPxweva^t&wu;9Hu?C%G6j#P00olJdD<&_O)dlVgG2FNk4BJj(& z7-g!~NC&N+xY(Ia{7ugPAw2krO)K_1x95G|$UUtbc^AMs0RP3$peSWpUu;b*N=J`(5!r}YeUCJaIl(Geb z^S{Fqmml6KUQIx+vQM__pQN-iK@2CgW4xbm<$=OZi!F<5Pz!VR)MDt|T#i@U*Q0mO z7?d*D9ky;lXl+=sUx@l+MdhKbO%(ZZ@iIt?puWCKEG3+SECzVrbr*Stys1UTBqQ{A zAMa?tT?3pL%mA4KCg`@u4A?*yAx-0B{`pLq7)Vgf7Ioas8Xf+jG%2GeEoIE0E86isjN7t2y_K5*#mVg$1T zWNoIX_V z36uL{v~U{U*=?U()5s~sy^u8+X>+kklwX_6-~EOzu`s3gz|?cJ9!JtMPN#sU6Tf8Y zpqf@;V4UAb>`wg|9~qnV`oPaY_X3Z)IER@BFn}EPU{}(SQ7?E75#Eiu2UaWd- zh1Ax+VqqDssV2_4D%uXMMJJDmo6DYbIO8|`k(!8j!MZ*pdi*)=h3)l7au!~}VS*3} z+#4(@Xjc9ep;-`6FF_FMFKX>zoKQ4NO3x0MR`Md{w%m^3i%^Bh}AE|Pp z`VaP}t!{J=+>px1Ydx;0$ zh1{Y8Gk13t)Yr8PszK&w0^iHC&QSRaca-DK?QYr`bPxd!`x$gq%+QLrFLr#@O#M8h=Hl#0>#BIR9x>|_6fb*;3#tmA1z#p`EOp!@FG&L8Y( z5JgP;&DAxn;+~^gDlYxtR zotX?YmHH>rsNbc{ee)Tl4G@?yJQR5a#&Za#{QX2mBw*J#Mt>JyHpS%Q#w7%pd7{n2(($ z`9c4haeGg^;;QJ0zNONVE`$}?p-2{=u|-&Um!85tiIUcnjwpi*m8WA!R} z+>(fJgE09xF=@`l^I;ol&GwPhSICm`YBec7Hj1(|DFHJh`SKvyuifs{7q601;B6vU zu6v&kYW|{N1p@Fq{sE{UocY1jMx94C4SS9FRjE2e(Mp&6MNhG6YSH?9qC~7nk+cUShP;;L~+HW>n{4jTWowQQimDi}-IR6yli?g*3b4wcUTu|*CWT3<3OmSRjTCin~jy>cEKdd5gZS9(N?ntDEvP7m+`~_ z#v?mCP=_d0DIrol5~ETk_~}I0`+nL&%simBvYaXeyW%oDLtlKLg`j|zSW38Scm6Rz zYqOl&i2`+UJy5iT1SG#YqBLr_5uCWIUfeBK`kSuIKMap6VZn3$o6aSfOxAX1%`kU3 zoOQ>J{eUzRD^B{>*#|mU>vks9TFw`-cPl^(^N)0su}47PAOClWD8x#6K1(pKEplwl zuv2!wJDAz|uG9hwWdTz7+u1uPqaG&&A4s6M@gr~G378wide&IFc);|gIPvQDTJB=3 zxy`AigessaGIJn!n^%2jssd;DoIQ2^U}=l@-Oa@Z;1G;&V3@ppoX=|+eN!%3_Kyzu zZ5QB!UVk}@NHHZ2uxYMFzhFZDr|T-AMeKC>^WloxEB4!Z;#P(kzr>k5lSgkh6+m zKLKedxb3)D&d;&G$XkA+&{q>4JmhCCm}|t}967t(u_)PAp3N7qul^)9rsIT=NW=(~ zG7*IW>~ySTE9nknAK)H<=$a%FQP}8?8&2P8_sO}zFjjt;eY%?4%d1OCt9s(kt65BY z!iwaykveatq<{UkVVZ%6_fXS<{x9t_1s%_^;ZWARuAF)^stNlu{TNkWsrS7`I1^ez zoedCmTAJ-$Io?h|Y2b4i2-l5McKqxu|nROoMODh@pK7Ic}U44FqK5e2u;ym(x;%IC#y@Mj=%z&uDlBS;!ihlaBN6 zfffZ(M=V+6m#AmefDYaz%)&{83^&pxjJ*Yv!Jdr=>ZK-}B`P7S4&NLh?`Es;6lwn{brFoN5hBoddkoXUoPm3&#tbKC*SiVzHe9*m7{p`e`R@?uN#j;uPh|T zZ8jT762YmCzt=l*r$dL?yP2rJy3P3?lY_H#sB){OBYs%^@0;=;w+*kvUpN(O09z7U z{Mw5JCuL(#dC-WL#MweZ6F1eRsP5?M`0$1rDtwjsp zun5qmtoTU$aYF8xtVKSBhEZwuO?{DUm2ss-v_nn5KtkgTmMyd0S4P$?}l52<^vOWtNr0iR+3u`3zXK9 z`{APg>$J$u;n3t>vE5p7#;S#mE8tr>F$_Cie|JY-3i>~{-MIY?wMSZJon4>oNFzl%tk8DksvLoAKz}CsgHixz??+sr(~FgG?B+Gq?$cy?6y)NDqybHv-FL8C+D1K`g*>b@8hCL zIjO0ZzG!M93PfQNn8a;$T%Z6us0ze)Hl+nQ2OECBU^eL-kR(?V^4yzu6-5?I+OIen zP?X`>$v78a<0?KuYl!!W3u+FOW)M~>gBwHG<`}%wVOme7lqg)U_)$xDY25oy{C#u& z=}~J>_i!`i1_-9d?z&s)sN0YdA`Y5C{+t*KYd6aVo}ZpxZ#J*M&8F58y_P&v#>CxC zfEy=|HBwa5ml@;}5l>jx>)nBG`6eKjH;z$rckqkI1Ob-vpCE@`yEEQNuq1Q4vhe&d z#1Dtdg!Z~-`sUJ~n(MY&jCP^?I;g=UB}VTk-8}#rNvi%{tA2%|erl+vPC%vZp8nCF z@k&2_y*0AQGW?nC%Yk~;v!bo5&BEQKe;TtvJojbl#o*?)VVj5q2C-)Yx0{x&zdowp z2u8fpvbELwj73}#EPJl&e9EMDAw%krp+9K*8BHd#Iguz`xsR>*8Ip#@U8;${f1U6I z(c*{dg=jzM$9Jw}p!z9Te_wS}{;m2wLH=qzLJ1Gc%bzoe!%=AhmAjAej=X7uwsK4| z%*{si35K#d@ww`%CwW;4xHBsx%KarZEv5e6T(Qq(K)YSS5FlCu0Qh6Z^Nkhb>dPCi zOM57A9nogPDdNN9RN{ErM%-N-Q`X|&_4BXn0sp1cT-UcK6hJ}?ZY#_eqiTO54R_`{ zDbwjzqVlaNm1#O!>w9UrXjh`tJ}W)Fyr|y{+u2F4`3HfXCOvrfttw%mE<`OI*wfFr zUxmYJPU`#7vuWTC?gS5>%j7y4{4OruN%-xsdde~KBx3)QWN4xa@p5#iN_cf$3EWW+nS4&Tg$e9s&idM_50BY3pB8DgG%GkX!Cp_hj@H7Q+OH39 zL2{;R9@p`98SYa#{*U4%tuLJS@#ra9yQeLxp%hsOP;?6ebrwtEth^K?gs){@=PZu-bI|7Kiz`e)`F_3E-cF^$iHD|J^zYbAf5*w7ds^__rv2$sfSv;HP&LY1Jp376?~_; z&dmB3p74m*;MbqXCPwgf(T0+$cv{aFe(YMY5O>w}=B}69q_3OH`Hr(4m^9d~=h0nf z6nB#br;X+dQ3=$K+gA=pERHOqs~SnQ6*u(v)htdC%{E)4dF791PD-s_hrv1twCoot zV{KN47pP@wGuKpJ=ZmiL$gNb~kJv!Pu^uc8u6^X}d-BE#kH;IEt=^5$2LQ#IrTRPlCP%;cIN*jbp6h-lUG!P8Q--+p>@oC~}bX+1Q{@;bkp_uHKbZZs*kopx%U z@J-U2td|PdSq?KEJ=YiGguJYJxL?gzYVwo3B470XoX)D-Ka^ApzVe1szSxak;J&6z z&?=d`?omgq-_5#v9#8_3y9AL)^;@}^hoF^td5s*hq*tGfIZb!KNUAX2`3ms0)!miT zMAP44lmA3&*DsXs?4r?lSn3&jlU(C@eU#O3=eLbf3l@&y(>7C)zf0#CuIM{8-El58 zfo03NVCk}SzlQAqaV_*UiPM?~%P5bs+xH+wY$nrb5z@463M1kf2VsDUf(D?G$g{w7Og9D)D6ZnOSlxJ3YA{ zE-Uwy(wMTl+)YQ^^*=dwSdwb@y0l>_nR(sZ-#1fqKMNR3J5M<=P`leqkmtR#y(r$b#CGhdnv-(oHPvo`@jP;oY6U&z zBNes7waBij+Zd-V-G7$7t8jdx-BEMBjdD2)5_ zQwA;@tIc{~r(Lsk;)&pF&|xJbrSb62OeurT0(8L8-0uE3%dm52^4#EY6#IJQiT)aa zZ^EMt#==yWCC%@AT-j%e__Nuy%qbIxn9+<_wkg&1u+TQWh`?WdP9}3&SQX>&NEc0* z0;aV}+G=A$(fPuRR*C`9+k3&M#LvTSHZqyslmNZr>~#bRHnd0YxAsPOERDe zKD!kmi^E_rR+_c;YJ+-D(;k)??6`9znMn+{8gKEk1vzz(n?s5%Ov%mFux0#mK*LPC z5$CZZ9_dwk&&$Ss`c@I=`WbzXyHhGH%IEjv4#~VFhssl_5H!5 zVtYx>-FtGMskNJPzrTqS(yZjS=a;+6jjKUN&=o52*m~)mla0jihq9LY+SBE>_Nb{e z$!~^4O`RoyQ%6vNQmULY%|rYiSFFa2a@uY#p*glr7T#258U`V}<(X^PsD_==9^MxF z#TCM~5$8PVz!i^qZeB5y0VOxo)_F)IOOO$aegssiiEWeM=e^cGZMSg=c=oSejR#iw zGcO_8+2tzdtHlYgM;f0kwMH@9b8MTm7GDaN@NwO*shpm=hS!Elf1wb7#Fo%HI$J~P zacl>s+`MYfDH>#;=eW_RuR)&Iy3Vh-0jVMU+qO#f(hs7PCX^Vfu75g{yw<&3r!OkN zEKRcnd|_+5KecT^7Tkmx95VezNv=gHe7@rAyjhMuR1Mp2Jfc*U7o*)k7}HddPNffA zRN}{qFPR)_u-Uq*Zqq?UH??ybV(K<%1H*eczog5BSPf_Uih(W9H{(96d{s&FkZIhr zVjFLfR76Qx@R_EdrmepHTU$l9j51j(HQ5=a@{Y1$-%dI*St4VpF0SCMy&89(RtQ` zbGVu*pwxI*YDGO!Rw_M~mZ$#fy76Jk2tjn!vwRmd0g2$4DqtQ z;*Jh;X$E_EoRR`HfCzP8srhGA z%c=OI6wkO#wMN~TG7?1;q*L=6a$@pMSaH?ksk}k+rN{!YwL%KL)C9GrzN;uhg(_jA z74SX0C*I4V;xb?LG#+B;Ol?Y8VR-8A-BOLkmoZheSEgLq z4ypx(F@rI ziAu3-5{ZK&@cTF^Ut$xgr28k)DQN}G3cJKO1tUJ4W)@PtDV9Ff5P{IP^e3E*{+hQH}ZbtZXL6aGe&1U45FL-QoSyM!5} z6JzL12uj7!Cx##rBfsD88<@YnDF+*fY!hoDXLE!14Zu0Z*V|KrUfRn``=6yOTtS7^ zYhJ5-YFe(f1oY-^I}xW`kAK!^PAt{y(eC#_<@536Y(PIPZddED)Fb_}wWnZsw-6@b zw}I5<0|Vie{AVE6Zqel&GGEhfV~jq{vF}@eYMy7f!;;y?4wlL;Ezuyna!6wJGNw-C z*vT8g60DC#GBT=fpk7}y18DrC8N6!tW0Xe6gsj^89PR1Fs!}e+-@ais_ka9`!oa*S z7fFh)kW*z0j1F6|!`=%f?PvI5nkDc*Cu}oZ$GXa(|VOJ0^_LdfpKWcg6?Pis!>)BKy-w#uO7T zkOZ#wg-y`NnI^KuOd=<*-EIrACyf#p(cr(%GM<5BHV^8#QY8v)+c7gKjbh zT-LZd&lU_bRgaVe_D%NnRj{ETbY7TwQIeK-6ja&fQIq``9kRF^vXY$;-x={RvVzeC z$4aFUMpear%7^le9^3k>O#99{PmmDQQ3G47d{1S)2hD|}YMceQIpSe%+!0+`)yJ6# zcs|z|MH+^7F7FQnInVQL9*=OOsdRe1p}n;_Ut)$>?RMYebGZY82|GIc({a0KRt|oW z5b2ZO4zKND8aog}%5nlFp2A#ui2tQ9LxvD@EsJ~BHRQ>-Q`q108(G`Ak>qP9L<8== z9k{ufK;gq#qay3s`C`T5048%CSler%gfk0S4!riGtwNZPTV>~dV=7M7)>hl0SL=bK zY<#Vs9Kr*H+$k@sfboLpam~}U*KlR~SLqrj@mL09vlZ(^LVHP5JF`CcE}k7F?wQ#^ zK7;*8PRT^~`R4|$x*1O?71tvLZW0<|IJe<3yUU-*J|4F#PN2owu+QH_F*;f``c|vh zp{z5lCJN&55J*X-uvkT}6m>{&EuaW0K7_O!9{ScGKC6KVWwYo@p?5?xAmOrI#mY%8 zJg+8Dsn8pbe+g9@4#(qcYRd4*8GAFU2X!zr&ac-+o@Gp`Gs>(qd^M9Xz7p-sDo9Fq zxg0?^y8aWF#*{EEwwC)H*%;$FXSAw(OdbwYh86=9Lc3dk@Hw|$_N}+8wzt9J=G9~f z$kdVnh zFm=oaqajiNJC%$`QVv_C7|xM{mMb2BNQ}*&gizLf^oIQ4eD(+C+Wlm?F-!3^R|4@J z-6C(6PH08@)ub4KdZTUbzIXbmwui*@J_nKCh+R($6ez7I@hq~FE%ONB9^$B<0?S4%E5gk01~S+}MPb4d zly7wiGv4;zr^YsCj8T+@&Mt;)EDQY5Cy7ZQnhRC&1(|GOX=R1l2_%z|)&>WanMu--r5*+6kRVeHnBu+kfBhGU$vY6FiT(2O^8z2QZHC;Fs<15qT4Y??bG4senbeRZs29 z^~qUQFceG2t9Nm4)B-V!8>qx=CeD}B!L9Pz*`RoCWtnRB#2j`Zl3a;>g4O|W; zb_rJtai1=2Dd}PL3LDQLJ&g!FMn@V{NDL#C#TG;*8L^otE?1nNvwN0nr!J!l%XoYrKzQRodIZ*%FmVj#*NftgYUg zs6dvjPCJ|vP|D<%Q z4r&hbGk{@}-7_7OAMp7p9&!J7S(jdZVKR{{E2i3GZX71E_`?uq=S~Lnth8!6z*BmobdLS(FvJhQyz;4}q>?fzpXq3F7@4`xH0W(-k%C zvwuqIqBX|Z9v>nHgH{fX8$ICpsh?(0Vs%tbi>%09S{*>L9*SGUeS|;kx&x7CMXs?Q zD!%Sz3q?43@k@xx3a|p6_@C%9hQj)>OdkX^otPA>FKvn?tW{fDQF45yGk~kcHq!FW z(+4@Gz#O96cMc2E$_0tdr~Nkk1}D=e3kl<2C~KOYf}fOHP(LBkrJa4BakzA8Jm2qP zS|@j835q?3vArh!YlCVAZ`u;lqeW#+QnSs9*_x&=-EOgFLT7>WBlBk!8OVY9_G@hd z3{DbG8&Sz6{`wNZxhxa4E{sbzDPI`^4{?m`sH6*tTRr2$BbpzQ|6+i_b2Nv;cVd`X zqsPd2Y=lT76||CZwnDj_-t72Haas8&+vt+9)lq!CZ*>8SG*r1$ya5qR{U|2 zB{2eJ@)Up<78x~w^VR!T!ac?Q(}o%q?*rJS4DUl)XKD^828gCD#Q*)8aKZ->;OT$< zi=POBiN?QQ@fPTf`~VP2j{m=a&HovMA}*ZjennsR7k9mX14ydE{{l$ZO90k^HGzh= zLm{EgaVR8o1@ICIDSm6H1u!N*Mo52FxEPGdroUNaxS*H_&0gj=sFm!~nfm{yLHECn z7yoJ_Jb9cLp2HaksD8pA;kn&JrshPY5ou3$W;;Z5o}EC>V05^zI@g@Y^jr!8N&pnJ z@KavxFOGftgFpfTK)(MxAixD6%>To`uMaN#gCZ(!SKYYp)_)?(!~+Z_hawIPP-AUf2@)I&`=sfHG*KsMP98*7o!P7_AiSI%{v1^=BHeRa@`InJDuWKv5nvao7IC@lQ;d< z;6uuTA@nmnR*2kU5H)-i42_i37?@zWctLXv*d?}`^2|g+ZAh`h^EKg7r0$jCIOnfi zUXovOojJve|AWzCP*GUH8E`o&udH!2ydLi{@lK>@u=b%fZM)3}c_+K6Ud)iQ{wDit zFf^8BV`G)RYAvHdJfF=GJstQIN<&f*z0qi@ynWoC(YmnSUXb_yGB_D9e8+j6su8^e z2X>k&@Bz|y$2-#A{NwTp{!Kf4SiA1gFTu}cWi$uV@gKMPA_Ab$e??bh7jR2M7oJ3t z!U^HnbPI7^AS?6rGU#-^t0ljVX!22o<~gn2x;fBwi%BpY7+mAJ*fsEE@wU2kvTmSCV7If(-DI|S zzmt{`l>7nTpSNp7S2WOFlgJCBxhjj-xD54l9+D-2fWq<_msDab63}dLw=HaKD3DIw zyGIs)e6J6`E?->mnn=mVTt$PqM4BiYi2|Ex%*>Sc|E`QLlur_`K z-TFi0O8$yqSwhwIFNwp}8E=V&I1S2eL}9Y>_7_(Z6wwRZLLj01!8QkNAJ&sFreg1m zAQe89kA^K;AlDuqCJUDUJ!S~l4^7KZKcS_K!xTfm%C(8uUHM9WgZmhd9bO<{w0Z!$ z(J|8oTUt#1I6kLuf3-hRmd-=&?@}$;`3vjowqjqBZ@r`(El8d9xf4Pey;Cio9@%&d z#Hc#NTqHMTwzHu-v$6nX#(mWnM)pa&VdEv zYU{5|)Q+s$q7{R5SQ_QA&(Tv5B@DJSdO{kwH{rWCIC478iV60=GVz;?c6I`!Wx1Qg z6}4!fmM9FclOg;P$=>5L>F*0RDA!puY${G654_Q*|$y)7bmU=f+o*Vyie zuIPfeD2zQ1>U2_wVT}#!XhUkTNlJuq4}+0KioWhe5U=5mPArqS?=ucFUtmj#lHvqb zo3s1-qA-zNjC1u8?$gP&@kor}dH+S+1!}#q09?V*Hm#SgRP--PD{%!(VyrS4_o}1 z-PrwN+b&@qEOR9LL}cSL$$#(+Q*1``%kfqBmVVftZI}^9TxLC`5i4`HR|SL-iJ|Ka zuf0HCX|`FdGjHP%5m0q;`f&6E;z*{UL#hgs z!45Gh6U#@Zxm+gSLj;+hH}tjQ{)KoyPVY$!3|C!Xmpjrtt~m_w;hE#rX%z`lQI`NQ z9Eq3avsv2y6~xpD4Z}Zju=h9!R%2a^*WxnS#3;6O!z|gWE2sY|Yw+hDzo$<0{uB=f z_d^>JM=IZySvH}8ie;`L-ws6H93r50N7h3_(*_ryv-MGQm6omQkMB3~rv`-MjX8O; z|0#?5aZ&%w@VFc*KDi2bW7vd2s`we7g>NrU}Z-e*tyq+{FZ_cJ!F#WwXc2FGfhCdlx8WvvN8KlY;QBo>ZAA7vjV0@086 zd2uO4RDR`s#7$PFsa95mu{Gn!HL)3O9+EHK4@PkO4{KP5*KX|QqEItTq-;o=-$j(3 zQiD&PfB$8<-60?zwO@fWHir$RlS8oI-YABnAk!sjdu&kdAa({0Mwe-4IhQsoLXED> zv`%C&fWB?!q2M-);cI<&rFc4gqoluXbM;0ab9dmNJ^ZqU z`r+{JwTi1r@{#>!wQ#_9Q0E9*WG{vZR9bZPbZUHN7`>FA2V){DAfZ`NS9z02GmMZo zziQ#qeL;m63N8ad;28WA#gcgi$qnkd_yZ9O+!_`>G`hJQo}(?`>>=+ zOJyYsHA*CqAD{3+^W{FhKl6|?gapB<>GzuHX@5$*CY?0-RIwZSnZZ(L3RfT|j#Sk7 zfuPM(r?x=v5YjVBfkIhhThr6~o(Vjet}La#`CY}=hr1;`#y-Ih9+b#gxIxSJU!&cf zCgBIblf~26(37RTk(I}37?CS&Q_yqmvmh%!Y^nXu*5d%xOxeTMiR?C8C?sA;j6xm7 zd2bR;5wTrv(A7E-;Cv6Aw_=&eA~V(6W~;H*-6_j)wM7tIrk;}~f^hW-=j_uEGQ9H@ zqudCBXLvY?caZX4Qifn5Jd(2S@i&$aS#}5}ne}W?E^}ha+VE-PXT>RjE#=|CRJFD< z5UCA*h!7cv5gOk;%=s*Ug`Sk;)4f`hmDMoFd^@oot+%zm&mu>MuBM zHBPV-X(^nbPtSt*+XW-TXaWI|6H1S^nB344(tQa2bQJK^kb4o4_{k|*!@=w$`yB$7 zFl3TVL&lUW+wxa$S^rYrTHp`{GH8e^1xHQ8=}Ujn{X?K1rM708p1jJ{C-T@I~!poa{!*g<`rHTx^8}xrO e#{D5R_6q4JpwzUd4;UrzBO)NlU&gEL``-YfXkMNG literal 0 HcmV?d00001 diff --git a/README.md b/README.md index 7d98270..7e4193a 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ Samples from [AASX Browser](https://admin-shell-io.com/5001/) were used for test - **AAS Full server**: See 'aas-api-webapp-full' folder in 'src'. Implementation of the entire interface collection as part of [Details of the Asset Administration Shell Part 2](https://www.plattform-i40.de/IP/Redaktion/EN/Downloads/Publikation/Details_of_the_Asset_Administration_Shell_Part2_V1.pdf) +![Sample Architecture](Assets/images/AzureIIoTHeroscenarioRefArch.png) + ## Security All servers use [Default Azure credentials](https://docs.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme) for authorization ([Managed Identities](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview)) diff --git a/scripts/azuredeployment/aasapiservicestdroles.json b/scripts/azuredeployment/aasapiservicestdroles.json new file mode 100644 index 0000000..7457339 --- /dev/null +++ b/scripts/azuredeployment/aasapiservicestdroles.json @@ -0,0 +1,11 @@ +[ + { + "allowedMemberTypes": [ + "Application" + ], + "description": "Contributors have to right to read and write AAS objects", + "displayName": "AAS Contributor", + "isEnabled": true, + "value": "AAS.ReadWrite" + } +] \ No newline at end of file diff --git a/scripts/azuredeployment/createAzureDigitalTwinsInstance.ps1 b/scripts/azuredeployment/createAzureDigitalTwinsInstance.ps1 index e69de29..6b43de2 100644 --- a/scripts/azuredeployment/createAzureDigitalTwinsInstance.ps1 +++ b/scripts/azuredeployment/createAzureDigitalTwinsInstance.ps1 @@ -0,0 +1,22 @@ +# Create Azure Digital Twin instance for the Shell and Discovery service +# uses the currently selected Azure subscription. Azure CLI version 2.14.0 or higher. + +param ( + [string] $dcloc = "West Europe", + [string] $rg = "aas-sample-rg", + [string] $dtName = "aasrepositorydigitaltwins" +) + +# 1. Check existenc of resource group and create it if it's not existing yet +$rsgExists = az group exists -n $rg +if ( $rsgExists -eq 'false') { + Write-Host "Resource group " $rg " doesn't exist. Creating it now." + az group create -l $dcloc -n $rg +} + +# 2. Create Azure Digital Twin instance +az dt create --dt-name $dtName -g $rg -l $dcloc --assign-identity true + +# 3. Create ADT Data Owner role for current user +$currAcc = $(az account show) | ConvertFrom-Json +az dt role-assignment create --dt-name $dtName -g $rg --assignee $currAcc.user.name --role "Azure Digital Twins Data Owner" diff --git a/scripts/azuredeployment/createAzureRedisCacheDBForRegistryServer.ps1 b/scripts/azuredeployment/createAzureRedisCacheDBForRegistryServer.ps1 index e69de29..44b3f18 100644 --- a/scripts/azuredeployment/createAzureRedisCacheDBForRegistryServer.ps1 +++ b/scripts/azuredeployment/createAzureRedisCacheDBForRegistryServer.ps1 @@ -0,0 +1,40 @@ +# Create Azure Redis Cache DB for the AAS Registry service +# uses the currently selected Azure subscription. Azure CLI version 2.14.0 or higher. +# Will create an Azure Redis Cache DB (Basic C0). +# If parameter 'appName' contains a valid Web App name in the same resource group, it will +# automatically set the Web Apps config para 'AASREGISTRYCACHECONNSTRING' to the connection +# string of the created Redis Cache DB. + +param ( + [string] $dcloc = "West Europe", + [string] $rg = "aas-sample-rg", + [string] $cacheName = "aasregistrycache", + [string] $cacheSku = "Basic", + [string] $cacheVmSize = "c0", + [string] $appName +) + +# 1. Check existenc of resource group and create it if it's not existing yet +$rsgExists = az group exists -n $rg +if ( $rsgExists -eq 'false') { + Write-Host "Resource group " $rg " doesn't exist. Creating it now." + az group create -l $dcloc -n $rg +} + +# 2. Create Redis Cache if it doesn't exist yet +$existingCaches=$(az redis list --query "[?name=='$cacheName']") +if ( $$existingCaches.length == 0) { + Write-Host "Redis cache with name " $cacheName " doesn't exist. Creating it now" + + $redis=$(az redis create --location $dcloc --name $cacheName -g $rg ` + --sku $cacheSku --vm-size $cacheVmSize --redis-version 6 --query [hostName,sslPort] --output tsv) + +# 3. Get connection string + $key=$(az redis list-keys --name $cacheName -g $rg --query primaryKey --output tsv) + $connString=$redis[0] + ":" + $redis[1] + ",password=" + $key + ",ssl=True,abortConnect=False" + Write-Host "Redis cache connection string: " $connString + +# 4. Assign the connection string to an App Setting in the Web App + if (-not $appName) + az webapp config appsettings set --name $appName -g $rg --settings "AASREGISTRYCACHECONNSTRING=$connString" +} \ No newline at end of file diff --git a/scripts/azuredeployment/createWebApiAppForService.ps1 b/scripts/azuredeployment/createWebApiAppForService.ps1 new file mode 100644 index 0000000..dbd35d5 --- /dev/null +++ b/scripts/azuredeployment/createWebApiAppForService.ps1 @@ -0,0 +1,34 @@ +# Create Azure Web Api app for AAS Services +# uses the currently selected Azure subscription. Azure CLI version 2.14.0 or higher. + +param ( + [string] $dcloc = "West Europe", + [string] $rg = "aas-sample-rg", + [string] $planName = "aasappserviceplan", + [string] $planSku = "B1", + [string] $appName = "aasapiservice" +) + +# 1. Check existenc of resource group and create it if it's not existing yet +$rsgExists = az group exists -n $rg +if ( $rsgExists -eq 'false') { + Write-Host "Resource group " $rg " doesn't exist. Creating it now." + az group create -l $dcloc -n $rg +} + +# 2. Create App service plan if it doesn't exist yet +$existingPlan=$(az appservice plan list -g $rg --query "[?name=='$planName']") +if ($existingPlan.length == 0) { + az appservice plan create --name $planName --g $rg --location $dcloc --is-linux --sku $planSku +} + +# 3. Create Web app +az webapp create -g $rg -p $planName -n $appName --runtime "DOTNETCORE|3.1" --assign-identity + +# TODO Set 'kind' to "linux,api" with 'az resource update' to enable API features in portal + +# 4. Create App registration for Web app +$clientId = $(az ad app create --display-name $appName --available-to-other-tenants false --app-roles @aasapiservicestdroles.json --query appId -o tsv) + +# 5. Set Azure AD configurtion in Web app +az webapp config appsettings set --name $appName -g $rg --settings "AzureAd__ClientId=$clientId" \ No newline at end of file From 0dd887e5ef07424dee413b0530dd2455f2d6bc46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Mayrb=C3=A4url?= Date: Thu, 20 Jan 2022 18:09:20 +0100 Subject: [PATCH 4/5] Completed Registry service with Submodel ops --- .../Interfaces/SubmodelRegistry.cs | 19 +++ .../AAS WebApp full.csproj | 2 + ...AdministrationShellRegistryInterfaceApi.cs | 106 +++++++----- .../SubmodelRegistryInterfaceApi.cs | 97 ++++++----- src/aas-api-webapp-full/Startup.cs | 7 + .../SubmodelRegistryInterfaceApi.cs | 153 +++++++++++++++++ src/aas-registry-service/AASRegistry.cs | 2 +- .../RedisImpl/RedisAASRegistry.cs | 160 +++++++++++++++++- 8 files changed, 466 insertions(+), 80 deletions(-) create mode 100644 src/aas-api-models/Interfaces/SubmodelRegistry.cs create mode 100644 src/aas-api-webapp-registry/Controllers/SubmodelRegistryInterfaceApi.cs diff --git a/src/aas-api-models/Interfaces/SubmodelRegistry.cs b/src/aas-api-models/Interfaces/SubmodelRegistry.cs new file mode 100644 index 0000000..fc35ef4 --- /dev/null +++ b/src/aas-api-models/Interfaces/SubmodelRegistry.cs @@ -0,0 +1,19 @@ +using AAS.API.Models; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace AAS.API.Interfaces +{ + public interface SubmodelRegistry + { + public Task> GetAllSubmodelDescriptors(); + + public Task GetSubmodelDescriptorById(string submodelIdentifier); + + public Task CreateSubmodelDescriptor(SubmodelDescriptor submodelDescriptor); + + public Task UpdateSubmodelDescriptorById(string submodelIdentifier, SubmodelDescriptor submodelDescriptor); + + public Task DeleteSubmodelDescriptorById(string idsubmodelIdentifier); + } +} diff --git a/src/aas-api-webapp-full/AAS WebApp full.csproj b/src/aas-api-webapp-full/AAS WebApp full.csproj index 1dfc7fb..a474311 100644 --- a/src/aas-api-webapp-full/AAS WebApp full.csproj +++ b/src/aas-api-webapp-full/AAS WebApp full.csproj @@ -8,6 +8,7 @@ AASAPIServerFull AASAPIServerFull AAS.API.Server.Full + bf8383c6-1e08-4107-b128-89b6ddb0562f @@ -28,6 +29,7 @@ + diff --git a/src/aas-api-webapp-full/Controllers/AssetAdministrationShellRegistryInterfaceApi.cs b/src/aas-api-webapp-full/Controllers/AssetAdministrationShellRegistryInterfaceApi.cs index f86f702..109da8c 100644 --- a/src/aas-api-webapp-full/Controllers/AssetAdministrationShellRegistryInterfaceApi.cs +++ b/src/aas-api-webapp-full/Controllers/AssetAdministrationShellRegistryInterfaceApi.cs @@ -18,6 +18,9 @@ using Microsoft.AspNetCore.Authorization; using AAS.API.Models; +using Microsoft.Extensions.Logging; +using AAS.API.Registry; +using System.Web; namespace AAS.API.WebApp.Controllers { @@ -27,7 +30,17 @@ namespace AAS.API.WebApp.Controllers [Authorize] [ApiController] public class AssetAdministrationShellRegistryInterfaceApiController : ControllerBase - { + { + private readonly ILogger _logger; + + private AASRegistry registryService; + + public AssetAdministrationShellRegistryInterfaceApiController(ILogger log, AASRegistry registry) + { + _logger = log; + registryService = registry; + } + /// /// Deletes an Asset Administration Shell Descriptor, i.e. de-registers an AAS /// @@ -38,13 +51,21 @@ public class AssetAdministrationShellRegistryInterfaceApiController : Controller [ValidateModelState] [SwaggerOperation("DeleteAssetAdministrationShellDescriptorById")] public virtual IActionResult DeleteAssetAdministrationShellDescriptorById([FromRoute][Required]string aasIdentifier) - { - //TODO: Uncomment the next line to return response 204 or use other options such as return this.NotFound(), return this.BadRequest(..), ... - // return StatusCode(204); + { + _logger.LogInformation($"DeleteAssetAdministrationShellDescriptorById called for Asset identifier '{aasIdentifier}'"); - throw new NotImplementedException(); + if (registryService == null) + { + _logger.LogError("Invalid setup. No Registry service configured. Check DI setup"); + throw new AASRegistryException("Invalid setup. No Registry service configured. Check DI setup"); + } + + registryService.DeleteAssetAdministrationShellDescriptorById(HttpUtility.UrlDecode(aasIdentifier)).GetAwaiter().GetResult(); + + return StatusCode(204); } + /* /// /// Deletes a Submodel Descriptor, i.e. de-registers a submodel /// @@ -62,6 +83,7 @@ public virtual IActionResult DeleteSubmodelDescriptorByIdAASRegistry([FromRoute] throw new NotImplementedException(); } + */ /// /// Returns all Asset Administration Shell Descriptors @@ -74,17 +96,11 @@ public virtual IActionResult DeleteSubmodelDescriptorByIdAASRegistry([FromRoute] [SwaggerResponse(statusCode: 200, type: typeof(List), description: "Requested Asset Administration Shell Descriptors")] public virtual IActionResult GetAllAssetAdministrationShellDescriptors() { - //TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ... - // return StatusCode(200, default(List)); - string exampleJson = null; - exampleJson = "[ {\n \"identification\" : \"identification\",\n \"idShort\" : \"idShort\",\n \"specificAssetIds\" : [ \"\", \"\" ],\n \"administration\" : {\n \"version\" : \"version\",\n \"revision\" : \"revision\"\n },\n \"description\" : [ {\n \"language\" : \"language\",\n \"text\" : \"text\"\n }, {\n \"language\" : \"language\",\n \"text\" : \"text\"\n } ],\n \"submodelDescriptors\" : [ {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n }, {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n } ],\n \"globalAssetId\" : \"\"\n}, {\n \"identification\" : \"identification\",\n \"idShort\" : \"idShort\",\n \"specificAssetIds\" : [ \"\", \"\" ],\n \"administration\" : {\n \"version\" : \"version\",\n \"revision\" : \"revision\"\n },\n \"description\" : [ {\n \"language\" : \"language\",\n \"text\" : \"text\"\n }, {\n \"language\" : \"language\",\n \"text\" : \"text\"\n } ],\n \"submodelDescriptors\" : [ {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n }, {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n } ],\n \"globalAssetId\" : \"\"\n} ]"; - - var example = exampleJson != null - ? JsonConvert.DeserializeObject>(exampleJson) - : default(List); //TODO: Change the data returned - return new ObjectResult(example); + // Currently not supported. Redis Cache doesn't have 'list all' operations + return StatusCode(200, default(List)); } + /* /// /// Returns all Submodel Descriptors /// @@ -107,6 +123,7 @@ public virtual IActionResult GetAllSubmodelDescriptorsAASRegistry([FromRoute][Re : default(List); //TODO: Change the data returned return new ObjectResult(example); } + */ /// /// Returns a specific Asset Administration Shell Descriptor @@ -119,18 +136,19 @@ public virtual IActionResult GetAllSubmodelDescriptorsAASRegistry([FromRoute][Re [SwaggerOperation("GetAssetAdministrationShellDescriptorById")] [SwaggerResponse(statusCode: 200, type: typeof(AssetAdministrationShellDescriptor), description: "Requested Asset Administration Shell Descriptor")] public virtual IActionResult GetAssetAdministrationShellDescriptorById([FromRoute][Required]string aasIdentifier) - { - //TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ... - // return StatusCode(200, default(AssetAdministrationShellDescriptor)); - string exampleJson = null; - exampleJson = "{\n \"identification\" : \"identification\",\n \"idShort\" : \"idShort\",\n \"specificAssetIds\" : [ \"\", \"\" ],\n \"administration\" : {\n \"version\" : \"version\",\n \"revision\" : \"revision\"\n },\n \"description\" : [ {\n \"language\" : \"language\",\n \"text\" : \"text\"\n }, {\n \"language\" : \"language\",\n \"text\" : \"text\"\n } ],\n \"submodelDescriptors\" : [ {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n }, {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n } ],\n \"globalAssetId\" : \"\"\n}"; - - var example = exampleJson != null - ? JsonConvert.DeserializeObject(exampleJson) - : default(AssetAdministrationShellDescriptor); //TODO: Change the data returned - return new ObjectResult(example); + { + _logger.LogInformation($"GetAssetAdministrationShellDescriptorById called for Asset identifier '{aasIdentifier}'"); + + if (registryService == null) + { + _logger.LogError("Invalid setup. No Registry service configured. Check DI setup"); + throw new AASRegistryException("Invalid setup. No Registry service configured. Check DI setup"); + } + + return new ObjectResult(registryService.GetAssetAdministrationShellDescriptorById(HttpUtility.UrlDecode(aasIdentifier)).GetAwaiter().GetResult()); } + /* /// /// Returns a specific Submodel Descriptor /// @@ -154,6 +172,7 @@ public virtual IActionResult GetSubmodelDescriptorByIdAASRegistry([FromRoute][Re : default(SubmodelDescriptor); //TODO: Change the data returned return new ObjectResult(example); } + */ /// /// Creates a new Asset Administration Shell Descriptor, i.e. registers an AAS @@ -166,18 +185,19 @@ public virtual IActionResult GetSubmodelDescriptorByIdAASRegistry([FromRoute][Re [SwaggerOperation("PostAssetAdministrationShellDescriptor")] [SwaggerResponse(statusCode: 201, type: typeof(AssetAdministrationShellDescriptor), description: "Asset Administration Shell Descriptor created successfully")] public virtual IActionResult PostAssetAdministrationShellDescriptor([FromBody]AssetAdministrationShellDescriptor body) - { - //TODO: Uncomment the next line to return response 201 or use other options such as return this.NotFound(), return this.BadRequest(..), ... - // return StatusCode(201, default(AssetAdministrationShellDescriptor)); - string exampleJson = null; - exampleJson = "{\n \"identification\" : \"identification\",\n \"idShort\" : \"idShort\",\n \"specificAssetIds\" : [ \"\", \"\" ],\n \"administration\" : {\n \"version\" : \"version\",\n \"revision\" : \"revision\"\n },\n \"description\" : [ {\n \"language\" : \"language\",\n \"text\" : \"text\"\n }, {\n \"language\" : \"language\",\n \"text\" : \"text\"\n } ],\n \"submodelDescriptors\" : [ {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n }, {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n } ],\n \"globalAssetId\" : \"\"\n}"; - - var example = exampleJson != null - ? JsonConvert.DeserializeObject(exampleJson) - : default(AssetAdministrationShellDescriptor); //TODO: Change the data returned - return new ObjectResult(example); + { + _logger.LogInformation($"PostAssetAdministrationShellDescriptor called for AAS with identifier '{body.Identification}'"); + + if (registryService == null) + { + _logger.LogError("Invalid setup. No Registry service configured. Check DI setup"); + throw new AASRegistryException("Invalid setup. No Registry service configured. Check DI setup"); + } + + return StatusCode(201, registryService.CreateAssetAdministrationShellDescriptor(body).GetAwaiter().GetResult()); } + /* /// /// Creates a new Submodel Descriptor, i.e. registers a submodel /// @@ -201,6 +221,7 @@ public virtual IActionResult PostSubmodelDescriptorAASRegistry([FromBody]Submode : default(SubmodelDescriptor); //TODO: Change the data returned return new ObjectResult(example); } + */ /// /// Updates an existing Asset Administration Shell Descriptor @@ -213,13 +234,21 @@ public virtual IActionResult PostSubmodelDescriptorAASRegistry([FromBody]Submode [ValidateModelState] [SwaggerOperation("PutAssetAdministrationShellDescriptorById")] public virtual IActionResult PutAssetAdministrationShellDescriptorById([FromBody]AssetAdministrationShellDescriptor body, [FromRoute][Required]string aasIdentifier) - { - //TODO: Uncomment the next line to return response 204 or use other options such as return this.NotFound(), return this.BadRequest(..), ... - // return StatusCode(204); + { + _logger.LogInformation($"PutAssetAdministrationShellDescriptorById called for AAS with identifier '{body.Identification}'"); - throw new NotImplementedException(); + if (registryService == null) + { + _logger.LogError("Invalid setup. No Registry service configured. Check DI setup"); + throw new AASRegistryException("Invalid setup. No Registry service configured. Check DI setup"); + } + + registryService.UpdateAssetAdministrationShellDescriptorById(body, HttpUtility.UrlDecode(aasIdentifier)).GetAwaiter().GetResult(); + + return StatusCode(204); } + /* /// /// Updates an existing Submodel Descriptor /// @@ -238,5 +267,6 @@ public virtual IActionResult PutSubmodelDescriptorByIdAASRegistry([FromBody]Subm throw new NotImplementedException(); } + */ } } diff --git a/src/aas-api-webapp-full/Controllers/SubmodelRegistryInterfaceApi.cs b/src/aas-api-webapp-full/Controllers/SubmodelRegistryInterfaceApi.cs index 792ad1d..1418eee 100644 --- a/src/aas-api-webapp-full/Controllers/SubmodelRegistryInterfaceApi.cs +++ b/src/aas-api-webapp-full/Controllers/SubmodelRegistryInterfaceApi.cs @@ -11,13 +11,15 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Annotations; -using Swashbuckle.AspNetCore.SwaggerGen; using Newtonsoft.Json; using System.ComponentModel.DataAnnotations; using AAS.API.Attributes; using Microsoft.AspNetCore.Authorization; using AAS.API.Models; +using Microsoft.Extensions.Logging; +using AAS.API.Registry; +using System.Web; namespace AAS.API.WebApp.Controllers { @@ -27,7 +29,17 @@ namespace AAS.API.WebApp.Controllers [Authorize] [ApiController] public class SubmodelRegistryInterfaceApiController : ControllerBase - { + { + private readonly ILogger _logger; + + private AASRegistry registryService; + + public SubmodelRegistryInterfaceApiController(ILogger log, AASRegistry registry) + { + _logger = log; + registryService = registry; + } + /// /// Deletes a Submodel Descriptor, i.e. de-registers a submodel /// @@ -38,11 +50,18 @@ public class SubmodelRegistryInterfaceApiController : ControllerBase [ValidateModelState] [SwaggerOperation("DeleteSubmodelDescriptorById")] public virtual IActionResult DeleteSubmodelDescriptorById([FromRoute][Required]string submodelIdentifier) - { - //TODO: Uncomment the next line to return response 204 or use other options such as return this.NotFound(), return this.BadRequest(..), ... - // return StatusCode(204); + { + _logger.LogInformation($"DeleteSubmodelDescriptorById called for Submodel identifier '{submodelIdentifier}'"); + + if (registryService == null) + { + _logger.LogError("Invalid setup. No Registry service configured. Check DI setup"); + throw new AASRegistryException("Invalid setup. No Registry service configured. Check DI setup"); + } - throw new NotImplementedException(); + registryService.DeleteSubmodelDescriptorById(HttpUtility.UrlDecode(submodelIdentifier)).GetAwaiter().GetResult(); + + return StatusCode(204); } /// @@ -55,16 +74,9 @@ public virtual IActionResult DeleteSubmodelDescriptorById([FromRoute][Required]s [SwaggerOperation("GetAllSubmodelDescriptors")] [SwaggerResponse(statusCode: 200, type: typeof(List), description: "Requested Submodel Descriptors")] public virtual IActionResult GetAllSubmodelDescriptors() - { - //TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ... - // return StatusCode(200, default(List)); - string exampleJson = null; - exampleJson = "[ {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n}, {\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n} ]"; - - var example = exampleJson != null - ? JsonConvert.DeserializeObject>(exampleJson) - : default(List); //TODO: Change the data returned - return new ObjectResult(example); + { + // Currently not supported. Redis Cache doesn't have 'list all' operations + return StatusCode(200, default(List)); } /// @@ -79,15 +91,15 @@ public virtual IActionResult GetAllSubmodelDescriptors() [SwaggerResponse(statusCode: 200, type: typeof(SubmodelDescriptor), description: "Requested Submodel Descriptor")] public virtual IActionResult GetSubmodelDescriptorById([FromRoute][Required]string submodelIdentifier) { - //TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ... - // return StatusCode(200, default(SubmodelDescriptor)); - string exampleJson = null; - exampleJson = "{\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n}"; - - var example = exampleJson != null - ? JsonConvert.DeserializeObject(exampleJson) - : default(SubmodelDescriptor); //TODO: Change the data returned - return new ObjectResult(example); + _logger.LogInformation($"GetSubmodelDescriptorById called for Submodel identifier '{submodelIdentifier}'"); + + if (registryService == null) + { + _logger.LogError("Invalid setup. No Registry service configured. Check DI setup"); + throw new AASRegistryException("Invalid setup. No Registry service configured. Check DI setup"); + } + + return new ObjectResult(registryService.GetSubmodelDescriptorById(HttpUtility.UrlDecode(submodelIdentifier)).GetAwaiter().GetResult()); } /// @@ -101,16 +113,16 @@ public virtual IActionResult GetSubmodelDescriptorById([FromRoute][Required]stri [SwaggerOperation("PostSubmodelDescriptor")] [SwaggerResponse(statusCode: 201, type: typeof(SubmodelDescriptor), description: "Submodel Descriptor created successfully")] public virtual IActionResult PostSubmodelDescriptor([FromBody]SubmodelDescriptor body) - { - //TODO: Uncomment the next line to return response 201 or use other options such as return this.NotFound(), return this.BadRequest(..), ... - // return StatusCode(201, default(SubmodelDescriptor)); - string exampleJson = null; - exampleJson = "{\n \"idShort\" : \"idShort\",\n \"description\" : [ null, null ]\n}"; - - var example = exampleJson != null - ? JsonConvert.DeserializeObject(exampleJson) - : default(SubmodelDescriptor); //TODO: Change the data returned - return new ObjectResult(example); + { + _logger.LogInformation($"PostSubmodelDescriptor called for Submodel with identifier '{body.Identification}'"); + + if (registryService == null) + { + _logger.LogError("Invalid setup. No Registry service configured. Check DI setup"); + throw new AASRegistryException("Invalid setup. No Registry service configured. Check DI setup"); + } + + return StatusCode(201, registryService.CreateSubmodelDescriptor(body).GetAwaiter().GetResult()); } /// @@ -124,11 +136,18 @@ public virtual IActionResult PostSubmodelDescriptor([FromBody]SubmodelDescriptor [ValidateModelState] [SwaggerOperation("PutSubmodelDescriptorById")] public virtual IActionResult PutSubmodelDescriptorById([FromBody]SubmodelDescriptor body, [FromRoute][Required]string submodelIdentifier) - { - //TODO: Uncomment the next line to return response 204 or use other options such as return this.NotFound(), return this.BadRequest(..), ... - // return StatusCode(204); + { + _logger.LogInformation($"PutSubmodelDescriptorById called for Submodel with identifier '{body.Identification}'"); + + if (registryService == null) + { + _logger.LogError("Invalid setup. No Registry service configured. Check DI setup"); + throw new AASRegistryException("Invalid setup. No Registry service configured. Check DI setup"); + } + + registryService.UpdateSubmodelDescriptorById(HttpUtility.UrlDecode(submodelIdentifier), body).GetAwaiter().GetResult(); - throw new NotImplementedException(); + return StatusCode(204); } } } diff --git a/src/aas-api-webapp-full/Startup.cs b/src/aas-api-webapp-full/Startup.cs index f5634f9..35bb7fd 100644 --- a/src/aas-api-webapp-full/Startup.cs +++ b/src/aas-api-webapp-full/Startup.cs @@ -28,6 +28,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Identity.Web; using Microsoft.AspNetCore.Authorization; +using AAS.API.Registry; namespace AAS.API.Full.Server { @@ -136,6 +137,12 @@ public void ConfigureServices(IServiceCollection services) services.AddHttpClient(); services.AddSingleton(); + + services.AddStackExchangeRedisCache(setupAction => + { + setupAction.Configuration = Configuration.GetConnectionString("RedisCache"); + }); + services.AddSingleton(); } /// diff --git a/src/aas-api-webapp-registry/Controllers/SubmodelRegistryInterfaceApi.cs b/src/aas-api-webapp-registry/Controllers/SubmodelRegistryInterfaceApi.cs new file mode 100644 index 0000000..1418eee --- /dev/null +++ b/src/aas-api-webapp-registry/Controllers/SubmodelRegistryInterfaceApi.cs @@ -0,0 +1,153 @@ +/* + * DotAAS Part 2 | HTTP/REST | Entire Interface Collection + * + * The entire interface collection as part of Details of the Asset Administration Shell Part 2 + * + * OpenAPI spec version: Final-Draft + * + * Generated by: https://github.com/swagger-api/swagger-codegen.git + */ +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; +using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using AAS.API.Attributes; + +using Microsoft.AspNetCore.Authorization; +using AAS.API.Models; +using Microsoft.Extensions.Logging; +using AAS.API.Registry; +using System.Web; + +namespace AAS.API.WebApp.Controllers +{ + /// + /// + /// + [Authorize] + [ApiController] + public class SubmodelRegistryInterfaceApiController : ControllerBase + { + private readonly ILogger _logger; + + private AASRegistry registryService; + + public SubmodelRegistryInterfaceApiController(ILogger log, AASRegistry registry) + { + _logger = log; + registryService = registry; + } + + /// + /// Deletes a Submodel Descriptor, i.e. de-registers a submodel + /// + /// The Submodel’s unique id (BASE64-URL-encoded) + /// Submodel Descriptor deleted successfully + [HttpDelete] + [Route("api/v1/registry/submodel-descriptors/{submodelIdentifier}")] + [ValidateModelState] + [SwaggerOperation("DeleteSubmodelDescriptorById")] + public virtual IActionResult DeleteSubmodelDescriptorById([FromRoute][Required]string submodelIdentifier) + { + _logger.LogInformation($"DeleteSubmodelDescriptorById called for Submodel identifier '{submodelIdentifier}'"); + + if (registryService == null) + { + _logger.LogError("Invalid setup. No Registry service configured. Check DI setup"); + throw new AASRegistryException("Invalid setup. No Registry service configured. Check DI setup"); + } + + registryService.DeleteSubmodelDescriptorById(HttpUtility.UrlDecode(submodelIdentifier)).GetAwaiter().GetResult(); + + return StatusCode(204); + } + + /// + /// Returns all Submodel Descriptors + /// + /// Requested Submodel Descriptors + [HttpGet] + [Route("api/v1/registry/submodel-descriptors")] + [ValidateModelState] + [SwaggerOperation("GetAllSubmodelDescriptors")] + [SwaggerResponse(statusCode: 200, type: typeof(List), description: "Requested Submodel Descriptors")] + public virtual IActionResult GetAllSubmodelDescriptors() + { + // Currently not supported. Redis Cache doesn't have 'list all' operations + return StatusCode(200, default(List)); + } + + /// + /// Returns a specific Submodel Descriptor + /// + /// The Submodel’s unique id (BASE64-URL-encoded) + /// Requested Submodel Descriptor + [HttpGet] + [Route("api/v1/registry/submodel-descriptors/{submodelIdentifier}")] + [ValidateModelState] + [SwaggerOperation("GetSubmodelDescriptorById")] + [SwaggerResponse(statusCode: 200, type: typeof(SubmodelDescriptor), description: "Requested Submodel Descriptor")] + public virtual IActionResult GetSubmodelDescriptorById([FromRoute][Required]string submodelIdentifier) + { + _logger.LogInformation($"GetSubmodelDescriptorById called for Submodel identifier '{submodelIdentifier}'"); + + if (registryService == null) + { + _logger.LogError("Invalid setup. No Registry service configured. Check DI setup"); + throw new AASRegistryException("Invalid setup. No Registry service configured. Check DI setup"); + } + + return new ObjectResult(registryService.GetSubmodelDescriptorById(HttpUtility.UrlDecode(submodelIdentifier)).GetAwaiter().GetResult()); + } + + /// + /// Creates a new Submodel Descriptor, i.e. registers a submodel + /// + /// Submodel Descriptor object + /// Submodel Descriptor created successfully + [HttpPost] + [Route("api/v1/registry/submodel-descriptors")] + [ValidateModelState] + [SwaggerOperation("PostSubmodelDescriptor")] + [SwaggerResponse(statusCode: 201, type: typeof(SubmodelDescriptor), description: "Submodel Descriptor created successfully")] + public virtual IActionResult PostSubmodelDescriptor([FromBody]SubmodelDescriptor body) + { + _logger.LogInformation($"PostSubmodelDescriptor called for Submodel with identifier '{body.Identification}'"); + + if (registryService == null) + { + _logger.LogError("Invalid setup. No Registry service configured. Check DI setup"); + throw new AASRegistryException("Invalid setup. No Registry service configured. Check DI setup"); + } + + return StatusCode(201, registryService.CreateSubmodelDescriptor(body).GetAwaiter().GetResult()); + } + + /// + /// Updates an existing Submodel Descriptor + /// + /// Submodel Descriptor object + /// The Submodel’s unique id (BASE64-URL-encoded) + /// Submodel Descriptor updated successfully + [HttpPut] + [Route("api/v1/registry/submodel-descriptors/{submodelIdentifier}")] + [ValidateModelState] + [SwaggerOperation("PutSubmodelDescriptorById")] + public virtual IActionResult PutSubmodelDescriptorById([FromBody]SubmodelDescriptor body, [FromRoute][Required]string submodelIdentifier) + { + _logger.LogInformation($"PutSubmodelDescriptorById called for Submodel with identifier '{body.Identification}'"); + + if (registryService == null) + { + _logger.LogError("Invalid setup. No Registry service configured. Check DI setup"); + throw new AASRegistryException("Invalid setup. No Registry service configured. Check DI setup"); + } + + registryService.UpdateSubmodelDescriptorById(HttpUtility.UrlDecode(submodelIdentifier), body).GetAwaiter().GetResult(); + + return StatusCode(204); + } + } +} diff --git a/src/aas-registry-service/AASRegistry.cs b/src/aas-registry-service/AASRegistry.cs index 0c65c85..56acc21 100644 --- a/src/aas-registry-service/AASRegistry.cs +++ b/src/aas-registry-service/AASRegistry.cs @@ -3,7 +3,7 @@ namespace AAS.API.Registry { - public interface AASRegistry : AAS.API.Interfaces.Registry + public interface AASRegistry : AAS.API.Interfaces.Registry, AAS.API.Interfaces.SubmodelRegistry { } public class AASRegistryException : AASServiceException diff --git a/src/aas-registry-service/RedisImpl/RedisAASRegistry.cs b/src/aas-registry-service/RedisImpl/RedisAASRegistry.cs index 125c999..cf8b286 100644 --- a/src/aas-registry-service/RedisImpl/RedisAASRegistry.cs +++ b/src/aas-registry-service/RedisImpl/RedisAASRegistry.cs @@ -56,6 +56,43 @@ public async Task CreateAssetAdministrationS return aasDesc; } + public async Task CreateSubmodelDescriptor(SubmodelDescriptor submodelDescriptor) + { + if (submodelDescriptor == null) + { + if (_logger != null) + _logger.LogError($"Parameter 'submodelDescriptor' must not be null"); + + throw new AASRegistryException($"Parameter 'submodelDescriptor' must not be null", new ArgumentNullException(nameof(submodelDescriptor))); + } + + if (_cache == null) + { + if (_logger != null) + _logger.LogError("Wrong DI configuration. No '_cache' configured"); + + throw new AASRegistryException("Wrong DI configuration. No '_cache' configured"); + } + + if (_logger != null) + _logger.LogTrace($"CreateSubmodelDescriptor called for submodel Descriptor with id '{submodelDescriptor.Identification}'"); + + try + { + await _cache.Set($"sm_{submodelDescriptor.Identification}", submodelDescriptor, new DistributedCacheEntryOptions()); + + } + catch (Exception ex) + { + if (_logger != null) + _logger.LogError($"Exception while setting a value in the cache: {ex.Message}"); + + throw new AASRegistryException("Exception while setting a value in the cache", ex); + } + + return submodelDescriptor; + } + public async Task DeleteAssetAdministrationShellDescriptorById(string aasIdentifier) { if (string.IsNullOrEmpty(aasIdentifier)) @@ -90,11 +127,52 @@ public async Task DeleteAssetAdministrationShellDescriptorById(string aasIdentif } } + public async Task DeleteSubmodelDescriptorById(string submodelIdentifier) + { + if (string.IsNullOrEmpty(submodelIdentifier)) + { + if (_logger != null) + _logger.LogError($"Parameter 'submodelIdentifier' must not be empty"); + + throw new AASRegistryException($"Parameter 'submodelIdentifier' must not be empty", new ArgumentNullException(nameof(submodelIdentifier))); + } + + if (_cache == null) + { + if (_logger != null) + _logger.LogError("Wrong DI configuration. No '_cache' configured"); + + throw new AASRegistryException("Wrong DI configuration. No '_cache' configured"); + } + + if (_logger != null) + _logger.LogTrace($"DeleteSubmodelDescriptorById called with id '{submodelIdentifier}'"); + + try + { + await _cache.Clear($"sm_{submodelIdentifier}"); + + } + catch (Exception ex) + { + if (_logger != null) + _logger.LogError($"Exception while deleting submodel descriptor for id '{submodelIdentifier}' from the cache: {ex.Message}"); + + throw new AASRegistryException($"Exception while deleting submodel descriptor for id '{submodelIdentifier}' from the cache", ex); + } + + } + public async Task> GetAllAssetAdministrationShellDescriptors() { throw new NotImplementedException(); } + public async Task> GetAllSubmodelDescriptors() + { + throw new NotImplementedException(); + } + public async Task GetAssetAdministrationShellDescriptorById(string aasIdentifier) { if (string.IsNullOrEmpty(aasIdentifier)) @@ -102,7 +180,7 @@ public async Task GetAssetAdministrationShel if (_logger != null) _logger.LogError($"Parameter 'aasIdentifier' must not be empty"); - throw new AASRegistryException($"Parameter 'aasDesc' must not be empty", new ArgumentNullException(nameof(aasIdentifier))); + throw new AASRegistryException($"Parameter 'aasIdentifier' must not be empty", new ArgumentNullException(nameof(aasIdentifier))); } if (_cache == null) @@ -130,6 +208,41 @@ public async Task GetAssetAdministrationShel } } + public async Task GetSubmodelDescriptorById(string submodelIdentifier) + { + if (string.IsNullOrEmpty(submodelIdentifier)) + { + if (_logger != null) + _logger.LogError($"Parameter 'submodelIdentifier' must not be empty"); + + throw new AASRegistryException($"Parameter 'submodelIdentifier' must not be empty", new ArgumentNullException(nameof(submodelIdentifier))); + } + + if (_cache == null) + { + if (_logger != null) + _logger.LogError("Wrong DI configuration. No '_cache' configured"); + + throw new AASRegistryException("Wrong DI configuration. No '_cache' configured"); + } + + if (_logger != null) + _logger.LogTrace($"GetSubmodelDescriptorById called with id '{submodelIdentifier}'"); + + try + { + return await _cache.Get($"sm_{submodelIdentifier}"); + + } + catch (Exception ex) + { + if (_logger != null) + _logger.LogError($"Exception while reading submodel descriptor for id '{submodelIdentifier}' from the cache: {ex.Message}"); + + throw new AASRegistryException($"Exception while reading submodel descriptor for id '{submodelIdentifier}' from the cache", ex); + } + } + public async Task UpdateAssetAdministrationShellDescriptorById(AssetAdministrationShellDescriptor aasDesc, string aasIdentifier) { if (aasDesc == null) @@ -145,7 +258,7 @@ public async Task UpdateAssetAdministrationShellDescriptorById(AssetAdministrati if (_logger != null) _logger.LogError($"Parameter 'aasIdentifier' must not be empty"); - throw new AASRegistryException($"Parameter 'aasDesc' must not be empty", new ArgumentNullException(nameof(aasIdentifier))); + throw new AASRegistryException($"Parameter 'aasIdentifier' must not be empty", new ArgumentNullException(nameof(aasIdentifier))); } if (_cache == null) @@ -172,5 +285,48 @@ public async Task UpdateAssetAdministrationShellDescriptorById(AssetAdministrati throw new AASRegistryException("Exception while updating a value in the cache", ex); } } + + public async Task UpdateSubmodelDescriptorById(string submodelIdentifier, SubmodelDescriptor submodelDescriptor) + { + if (submodelDescriptor == null) + { + if (_logger != null) + _logger.LogError($"Parameter 'submodelDescriptor' must not be null"); + + throw new AASRegistryException($"Parameter 'submodelDescriptor' must not be null", new ArgumentNullException(nameof(submodelDescriptor))); + } + + if (string.IsNullOrEmpty(submodelIdentifier)) + { + if (_logger != null) + _logger.LogError($"Parameter 'submodelIdentifier' must not be empty"); + + throw new AASRegistryException($"Parameter 'submodelIdentifier' must not be empty", new ArgumentNullException(nameof(submodelIdentifier))); + } + + if (_cache == null) + { + if (_logger != null) + _logger.LogError("Wrong DI configuration. No '_cache' configured"); + + throw new AASRegistryException("Wrong DI configuration. No '_cache' configured"); + } + + if (_logger != null) + _logger.LogTrace($"UpdateSubmodelDescriptorById called for Submodel Descriptor with id '{submodelIdentifier}'"); + + try + { + await _cache.Set(submodelIdentifier, submodelDescriptor, new DistributedCacheEntryOptions()); + + } + catch (Exception ex) + { + if (_logger != null) + _logger.LogError($"Exception while updating '{submodelIdentifier}' from the cache: {ex.Message}"); + + throw new AASRegistryException("Exception while updating a value in the cache", ex); + } + } } } From 0a52a9441e0caa44995d0d3b923311d83fb9ac66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Mayrb=C3=A4url?= Date: Thu, 20 Jan 2022 18:23:15 +0100 Subject: [PATCH 5/5] Fixed docker build error for Registry service --- src/aas-api-webapp-registry/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aas-api-webapp-registry/Dockerfile b/src/aas-api-webapp-registry/Dockerfile index 68a8479..3a72543 100644 --- a/src/aas-api-webapp-registry/Dockerfile +++ b/src/aas-api-webapp-registry/Dockerfile @@ -7,7 +7,7 @@ EXPOSE 443 FROM mcr.microsoft.com/dotnet/sdk:3.1 AS build WORKDIR /src -COPY ["src/aas-api-webapp-registry/AAS WebApp Registry.csproj", "src/AAS WebApp Registry/"] +COPY ["src/aas-api-webapp-registry/AAS WebApp Registry.csproj", "src/aas-api-webapp-registry/"] RUN dotnet restore "src/aas-api-webapp-registry/AAS WebApp Registry.csproj" COPY . . WORKDIR "/src/src/aas-api-webapp-registry"