PSSwaggerMetadata.psm1

#########################################################################################
#
# Copyright (c) Microsoft Corporation. All rights reserved.
#
# Licensed under the MIT license.
#
# PSSwagger Module
#
#########################################################################################
Microsoft.PowerShell.Core\Set-StrictMode -Version Latest

$SubScripts = @(
    'PSSwagger.Constants.ps1'
)
$SubScripts | ForEach-Object {. (Join-Path -Path $PSScriptRoot -ChildPath $_) -Force}

$SubModules = @(
    'SwaggerUtils.psm1',
    'Utilities.psm1',
    'Paths.psm1',
    'Definitions.psm1'
)
$SubModules | ForEach-Object {Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath $_) -Force -Scope Local -DisableNameChecking}

Microsoft.PowerShell.Utility\Import-LocalizedData  LocalizedData -filename PSSwagger.Resources.psd1

<#
.SYNOPSIS
    Creates PowerShell Metadata json file with PowerShell Extensions for the specified Swagger document.
 
.DESCRIPTION
    Creates PowerShell Metadata json file with PowerShell Extensions for the specified Swagger document.
    This file can be used to customize the PowerShell specific metadata like
    cmdlet name, parameter name, output format views, code generation settings, PowerShell Module metadata and other related metadata.
    PowerShell Metadata file name for <SwaggerSpecFileName>.json is <SwaggerSpecFileName>.psmeta.json.
    This <SwaggerSpecFileName>.psmeta.json file gets created under the same location as the specified swagger document path.
 
.EXAMPLE
    PS> New-PSSwaggerMetadataFile -SpecificationPath 'C:\SwaggerSpecs\BatchManagement.json'
    Generates 'C:\SwaggerSpecs\BatchManagement.psmeta.json' file with PowerShell extensions for customizing the PowerShell related metadata.
 
.EXAMPLE
    PS> New-PSSwaggerMetadataFile -SpecificationPath 'C:\SwaggerSpecs\BatchManagement.json' -Force
    Regenerates 'C:\SwaggerSpecs\BatchManagement.psmeta.json' file with PowerShell extensions for customizing the PowerShell related metadata.
   
.PARAMETER SpecificationPath
    Full Path to a Swagger based JSON spec.
 
.PARAMETER Force
    To replace the existing PowerShell Metadata file.
 
.INPUTS
 
.OUTPUTS
 
.NOTES
 
.LINK
 
#>

function New-PSSwaggerMetadataFile {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            ValueFromPipeline = $true)]
        [string] 
        $SpecificationPath,

        [Parameter(Mandatory = $false)]
        [switch]
        $Force
    )

    # Validate swagger path
    if (-not (Test-path -Path $SpecificationPath -PathType Leaf)) {
        throw $LocalizedData.SwaggerSpecPathNotExist -f ($SpecificationPath)
        return
    }

    $PSMetaFilePath = [regex]::replace($SpecificationPath, ".json$", ".psmeta.json")
    if ((-not $Force) -and (Test-Path -Path $PSMetaFilePath -PathType Leaf)) {
        Throw $LocalizedData.PSMetaFileExists -f ($PSMetaFilePath, $SpecificationPath)
    }

    $SwaggerSpecFilePaths = @()
    $AutoRestModeler = 'Swagger'    
    $jsonObject = ConvertFrom-Json -InputObject ((Get-Content -Path $SpecificationPath) -join [Environment]::NewLine) -ErrorAction Stop
    $SwaggerBaseDir = Split-Path -Path $SpecificationPath -Parent
    if ((Get-Member -InputObject $jsonObject -Name 'Documents') -and ($jsonObject.Documents.Count)) {
        $AutoRestModeler = 'CompositeSwagger'
        foreach ($document in $jsonObject.Documents) {
            if (Test-Path -Path $document -PathType Leaf) {
                $SwaggerSpecFilePaths += $document
            }
            elseif (Test-Path -Path (Join-Path -Path $SwaggerBaseDir -ChildPath $document) -PathType Leaf) {
                $SwaggerSpecFilePaths += Join-Path -Path $SwaggerBaseDir -ChildPath $document
            }
            else {
                throw $LocalizedData.PathNotFound -f ($document)
                return
            }
        }
    }
    else {
        $SwaggerSpecFilePaths += $SpecificationPath
    }

    $DefinitionFunctionsDetails = @{}
    $ParameterGroupCache = @{}
    $PathFunctionDetails = @{}
    $x_ms_path_FunctionDetails = @{}
    
    # Parse the JSON and populate the dictionary
    $ConvertToSwaggerDictionary_params = @{
        SwaggerSpecPath            = $SpecificationPath
        SwaggerSpecFilePaths       = $SwaggerSpecFilePaths
        DefinitionFunctionsDetails = $DefinitionFunctionsDetails
    }
    $swaggerDict = ConvertTo-SwaggerDictionary @ConvertToSwaggerDictionary_params

    $nameSpace = $swaggerDict['info'].NameSpace
    $models = $swaggerDict['info'].Models
    $swaggerMetaDict = @{
        SwaggerSpecPath      = $SpecificationPath
        SwaggerSpecFilePaths = $SwaggerSpecFilePaths
        AutoRestModeler      = $AutoRestModeler
    }

    foreach ($FilePath in $SwaggerSpecFilePaths) {
        $jsonObject = ConvertFrom-Json -InputObject ((Get-Content -Path $FilePath) -join [Environment]::NewLine) -ErrorAction Stop

        if (Get-Member -InputObject $jsonObject -Name 'Definitions') {
            # Handle the Definitions
            $jsonObject.Definitions.PSObject.Properties | ForEach-Object {
                $GetSwaggerSpecDefinitionInfo_params = @{
                    JsonDefinitionItemObject   = $_
                    Namespace                  = $Namespace
                    DefinitionFunctionsDetails = $DefinitionFunctionsDetails
                    Models                     = $models
                }
                Get-SwaggerSpecDefinitionInfo @GetSwaggerSpecDefinitionInfo_params
            }
        }

        if (Get-Member -InputObject $jsonObject -Name 'Paths') {
            # Handle the Paths
            $jsonObject.Paths.PSObject.Properties | ForEach-Object {
                $GetSwaggerSpecPathInfo_params = @{
                    JsonPathItemObject         = $_
                    PathFunctionDetails        = $PathFunctionDetails
                    SwaggerDict                = $swaggerDict
                    SwaggerMetaDict            = $swaggerMetaDict
                    DefinitionFunctionsDetails = $DefinitionFunctionsDetails
                    ParameterGroupCache        = $ParameterGroupCache
                }
                Get-SwaggerSpecPathInfo @GetSwaggerSpecPathInfo_params
            }
        }

        if (Get-Member -InputObject $jsonObject -Name 'x-ms-paths') {
            # Handle extended paths
            $jsonObject.'x-ms-paths'.PSObject.Properties | ForEach-Object {
                $GetSwaggerSpecPathInfo_params = @{
                    JsonPathItemObject         = $_
                    PathFunctionDetails        = $x_ms_path_FunctionDetails
                    SwaggerDict                = $swaggerDict
                    SwaggerMetaDict            = $swaggerMetaDict
                    DefinitionFunctionsDetails = $DefinitionFunctionsDetails
                    ParameterGroupCache        = $ParameterGroupCache
                }
                Get-SwaggerSpecPathInfo @GetSwaggerSpecPathInfo_params
            }
        }
    }

    $infoMetadata = Get-InfoPSMetadata -SwaggerDict $swaggerDict
    $definitionsMetadata = Get-DefinitionsPSMetadata -DefinitionFunctionsDetails $DefinitionFunctionsDetails -SwaggerDict $swaggerDict
    $pathsMetadata = Get-PathsPSMetadata -PathFunctionDetails $PathFunctionDetails    
    $globalParametersMetadata = Get-GlobalParametersPSMetadata -SwaggerDict $swaggerDict

    $psMetadata = [ordered]@{
        info  = $infoMetadata
        paths = $pathsMetadata
    }

    # Add x-ms-paths key if there are any swagger operations are specified under x-ms-paths.
    if (Get-HashtableKeyCount -Hashtable $x_ms_path_FunctionDetails) {
        $x_ms_pathsMetadata = Get-PathsPSMetadata -PathFunctionDetails $x_ms_path_FunctionDetails
        $psMetadata['x-ms-paths'] = $x_ms_pathsMetadata
    }
    
    $psMetadata['definitions'] = $definitionsMetadata
    $psMetadata['parameters'] = $globalParametersMetadata

    $psmetaJson = ConvertTo-Json -InputObject $psMetadata -Depth 100 | Format-JsonUtility

    if ($psmetaJson -and ($Force -or $pscmdlet.ShouldProcess($PSMetaFilePath, $LocalizedData.NewPSSwaggerMetadataFileOperationMessage))) {
        $OutFile_Params = @{
            InputObject = $psmetaJson
            FilePath    = $PSMetaFilePath
            Encoding    = 'ascii'
            Force       = $true
            Confirm     = $false
            WhatIf      = $false
        }
        Out-File @OutFile_Params

        Write-Verbose -Message ($LocalizedData.SuccessfullyGeneratedMetadataFile -f $PSMetaFilePath, $SpecificationPath)
    }
}

#region PSSwaggerMetadata Utilities

<#
    Helper function for getting the code generation settings and module info metadata.
#>

function Get-InfoPSMetadata {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [PSCustomObject]
        $SwaggerDict
    )

    $PSCodeGenerationSettings = [ordered]@{
        codeGenerator         = 'CSharp'
        nameSpacePrefix       = 'Microsoft.PowerShell.'
        noAssembly            = $false
        powerShellCorePath    = ''
        includeCoreFxAssembly = $false
        testBuild             = $false
        confirmBootstrap      = $false
        path                  = '.'
        symbolPath            = '.'
        serviceType           = ''
        customAuthCommand     = ''
        hostOverrideCommand   = ''
        noAuthChallenge       = ''
    }

    $PSModuleInfo = [ordered]@{
        name                 = $swaggerDict['Info'].ModuleName
        moduleVersion        = $swaggerDict['Info'].Version.ToString()
        guid                 = [guid]::NewGuid()
        description          = $swaggerDict['Info'].Description
        author               = $swaggerDict['Info'].ContactEmail
        companyName          = ''
        CopyRight            = $swaggerDict['Info'].LicenseName
        licenseUri           = $swaggerDict['Info'].LicenseUri
        projectUri           = $swaggerDict['Info'].ProjectUri
        helpInfoUri          = ''
        iconUri              = ''
        releaseNotes         = ''
        defaultCommandPrefix = ''
        tags                 = @()
    }

    return [ordered]@{
        'x-ps-code-generation-settings' = $PSCodeGenerationSettings
        'x-ps-module-info'              = $PSModuleInfo
    }
}

<#
    Helper function for getting the definitions metadata with
    cmdlet infos, parameter info and output format info.
#>

function Get-DefinitionsPSMetadata {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [PSCustomObject]
        $DefinitionFunctionsDetails,

        [Parameter(Mandatory = $true)]
        [PSCustomObject]
        $SwaggerDict
    )

    $definitionsMetadata = [ordered]@{}
    $DefinitionFunctionsDetails.GetEnumerator() | Foreach-Object {
        $definitionName = $_.Name
        $FunctionDetails = $_.Value

        $generateCommand = $false
        $generateOutputFormat = $false
        if ($FunctionDetails.IsModel) {
            $generateCommand = $true
            $generateOutputFormat = $true            
        }

        $x_ps_cmdlet_info = [ordered]@{
            name                 = "New-$($definitionName)Object"
            description          = $FunctionDetails.Description
            defaultParameterSet  = $definitionName
            generateCommand      = $generateCommand
            generateOutputFormat = $generateOutputFormat
        }

        $propertiesPSMetadata = [ordered]@{}
        $defaultFormatViewWidth = 10

        $TableColumnItemCount = 0
        $ParametersCount = Get-HashtableKeyCount -Hashtable $FunctionDetails.ParametersTable
        $SkipParameterList = @('id', 'tags')
        $Namespace = $SwaggerDict['info'].NameSpace

        $FunctionDetails.ParametersTable.GetEnumerator() | Foreach-Object {
            $parameterName = $_.Name
            $parameterDetails = $_.Value

            $x_ps_parameter_info = [ordered]@{
                name        = $parameterName
                description = $parameterDetails.Description
            }

            # Enable output format view for all properties when definition has 4 or less properties.
            # Otherwise add the first 4 properties with basic types by skipping the complex types, id and tags.
            if ($FunctionDetails.IsModel -and 
                (($ParametersCount -le 4) -or 
                    (($TableColumnItemCount -le 4) -and 
                        ($SkipParameterList -notcontains $parameterDetails.Name) -and 
                        (-not $ParameterDetails.Type.StartsWith($Namespace, [System.StringComparison]::OrdinalIgnoreCase))))) {
                $includeInOutputFormat = $true
                $formatViewPosition = $TableColumnItemCount
                $TableColumnItemCount = $TableColumnItemCount + 1
            }
            else {
                $includeInOutputFormat = $false
                # Position is specified as -1 so that module owner can edit this value with proper position number
                # if he/she decides to enable the output format for the specific property.
                $formatViewPosition = -1
            }

            $x_ps_output_format_info = [ordered]@{
                include  = $includeInOutputFormat
                position = $formatViewPosition
                width    = $defaultFormatViewWidth
            }

            $propertiesPSMetadata[$parameterDetails.OriginalParameterName] = [ordered]@{
                'x-ps-parameter-info'     = $x_ps_parameter_info
                'x-ps-output-format-info' = $x_ps_output_format_info
            }
        }
        
        $definitionsMetadata[$definitionName] = [ordered]@{
            'x-ps-cmdlet-infos' = @($x_ps_cmdlet_info)
            properties          = $propertiesPSMetadata
        }
    }

    return $definitionsMetadata
}

<#
    Helper function for getting the paths metadata with
    cmdlet infos and parameter info.
#>

function Get-PathsPSMetadata {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [PSCustomObject]
        $PathFunctionDetails
    )

    $pathsMetadata = [ordered]@{}
    $PathFunctionDetails.GetEnumerator() | Foreach-Object {
        $CommandName = $_.Name
        $FunctionDetails = $_.Value
        $defaultParameterSetName = ''
        $FunctionDetails.ParameterSetDetails | Foreach-Object {
            $ParameterSetDetail = $_
            # When multiple operations are combined into single cmdlet,
            # adding first parameterset as the default parameterset name.
            if (-not $defaultParameterSetName) {
                $defaultParameterSetName = $ParameterSetDetail.OperationId
            }

            $EndpointRelativePath = $ParameterSetDetail.EndpointRelativePath
            if ($pathsMetadata.Contains($EndpointRelativePath)) {
                $operationsPSMetadata = $pathsMetadata[$EndpointRelativePath]
            }
            else {
                $operationsPSMetadata = [ordered]@{}
            }

            $operationType = $ParameterSetDetail.OperationType
            if ($operationsPSMetadata.Contains($operationType)) {
                $opPSMetadata = $operationsPSMetadata[$operationType]
            }
            else {
                $opPSMetadata = [ordered]@{}
            }

            if ($opPSMetadata.Contains('x-ps-cmdlet-infos')) {
                $x_ps_cmdlet_infos = $opPSMetadata['x-ps-cmdlet-infos']
            }
            else {
                $x_ps_cmdlet_infos = @()
            }

            $x_ps_cmdlet_infos += [ordered]@{
                name                = $CommandName
                description         = $ParameterSetDetail.Description
                defaultParameterSet = $defaultParameterSetName
                generateCommand     = $true
            }
            $opPSMetadata['x-ps-cmdlet-infos'] = $x_ps_cmdlet_infos

            # For multiple cmdlet scenario like CreateAndUpdate,
            # if parameters are already populated for one cmdlet,
            # it is not required to process the parameters for other cmdlet for same operationId.
            if (-not $opPSMetadata.Contains('parameters')) {
                $parametersPSMetadata = [ordered]@{}
                $ParameterSetDetail.ParameterDetails.GetEnumerator() | Foreach-Object {
                    $paramDetails = $_.Value
                    $parameterName = $paramDetails.Name
                    if ($paramDetails.ContainsKey('OriginalParameterName') -and $paramDetails.OriginalParameterName) {
                        $x_ps_parameter_info = [ordered]@{
                            name        = $parameterName
                            description = $paramDetails.Description
                            flatten     = $false
                        }
                        $parametersPSMetadata[$paramDetails.OriginalParameterName] = [ordered]@{
                            'x-ps-parameter-info' = $x_ps_parameter_info
                        }
                    }
                }
                $opPSMetadata['parameters'] = $parametersPSMetadata
            }

            # Handle path level common parameters, if any
            $PathCommonParameters = $ParameterSetDetail.PathCommonParameters
            if (Get-HashtableKeyCount -Hashtable $PathCommonParameters) {
                $pathItemFieldName = 'parameters'
                if (-not $operationsPSMetadata.Contains($pathItemFieldName)) {
                    $pathItemFieldPSMetadata = [ordered]@{}
                    $PathCommonParameters.GetEnumerator() | Foreach-Object {
                        $paramDetails = $_.Value
                        $parameterName = $paramDetails.Name
                        if ($paramDetails.ContainsKey('OriginalParameterName') -and $paramDetails.OriginalParameterName) {
                            $x_ps_parameter_info = [ordered]@{
                                name        = $parameterName
                                description = $paramDetails.Description
                                flatten     = $false
                            }
                            $pathItemFieldPSMetadata[$paramDetails.OriginalParameterName] = [ordered]@{
                                'x-ps-parameter-info' = $x_ps_parameter_info
                            }
                        }
                    }
                    $operationsPSMetadata[$pathItemFieldName] = $pathItemFieldPSMetadata
                }                
            }
            
            $operationsPSMetadata[$operationType] = $opPSMetadata
            $pathsMetadata[$EndpointRelativePath] = $operationsPSMetadata
        }
    }
    return $pathsMetadata
}

<#
    Helper function for getting the parameter infos for the global parameters.
#>

function Get-GlobalParametersPSMetadata {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [PSCustomObject]
        $SwaggerDict
    )

    $globalParametersMetadata = [ordered]@{}
    $SwaggerDict.Parameters.GetEnumerator() | Foreach-Object {        
        $commonParameterKeyName = $_.Name
        $parameterDetails = $_.Value
        
        $x_ps_parameter_info = [ordered]@{
            name        = $parameterDetails.Name
            description = $parameterDetails.Description
            flatten     = $false
        }
        $parameterPSMetadata = [ordered]@{
            'x-ps-parameter-info' = $x_ps_parameter_info
        }
        $globalParametersMetadata[$commonParameterKeyName] = $parameterPSMetadata
    }
    
    return $globalParametersMetadata
}

<#
.DESCRIPTION
    Utility for formatting the json content.
    ConvertTo-Json cmdlet doesn't format the json content properly on Windows PowerShell.
   
.PARAMETER Json
  Json string value.
#>

function Format-JsonUtility {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]
        $Json
    )

    $indent = 0;
    ($json -Split '\n' | ForEach-Object {
            if (($_ -match ' [\}\]]') -or ($_ -match '[\}\]]$')) {
                # This line contains ] or }, decrement the indentation level
                if ($indent -gt 0) {
                    $indent--
                }
            }
            $line = (' ' * $indent * 2) + $_.TrimStart().Replace(': ', ': ')

            if (($_ -match ' [\{\[]') -or ($_ -match '^[\{\[]')) {
                # This line contains [ or {, increment the indentation level
                $indent++
            }

            $line
        }) -Join "`n"
}

#endregion PSSwaggerMetadata Utilities

Export-ModuleMember -Function New-PSSwaggerMetadataFile
# SIG # Begin signature block
# MIIarQYJKoZIhvcNAQcCoIIanjCCGpoCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUFj8tO9pLWD8hrqDIRSO5r+WJ
# kdGgghWAMIIEwjCCA6qgAwIBAgITMwAAALm8D05X42ZlOAAAAAAAuTANBgkqhkiG
# 9w0BAQUFADB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G
# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSEw
# HwYDVQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EwHhcNMTYwOTA3MTc1ODQ2
# WhcNMTgwOTA3MTc1ODQ2WjCBsjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEMMAoGA1UECxMDQU9DMScwJQYDVQQLEx5uQ2lwaGVyIERTRSBFU046
# NkJGNi0yRDUyLTkyQzExJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNl
# cnZpY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCdXDM6Nw8Ck6Kk
# 8k7KXa6ef90VvfETAqgHmtlBZbMr2580HCnjeUqVnMptYOf4SPDNDhtJ7Qc3PCk6
# GJ6J/fssnK9n/3QVnAmIBSINx6vUOasQIBIvf72aGP3Ax0OMx003HDcenhkn5+YJ
# 3IEMJMGN9AvoxZpNvvP2daLhVCLhtrvyPI4ZbWTmilwNQdI7KG6UQsEcVw9h+H/e
# QK3GUHpgjkAQIgLlxdl2GUzuyRB7w3q8IcL2knoiXyaJnu/8ZImBAUz/e9Y0hceH
# XSyLwm3yD7cTI/1NIoC3NCa4JNC0mIL34IiPmpxOsrYrnC8N56eB3RaqVTgpP0GT
# A/rYkXHBAgMBAAGjggEJMIIBBTAdBgNVHQ4EFgQUEDX8qfRQm15YRy1KztfgtxHB
# HfkwHwYDVR0jBBgwFoAUIzT42VJGcArtQPt2+7MrsMM1sw8wVAYDVR0fBE0wSzBJ
# oEegRYZDaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv
# TWljcm9zb2Z0VGltZVN0YW1wUENBLmNybDBYBggrBgEFBQcBAQRMMEowSAYIKwYB
# BQUHMAKGPGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljcm9z
# b2Z0VGltZVN0YW1wUENBLmNydDATBgNVHSUEDDAKBggrBgEFBQcDCDANBgkqhkiG
# 9w0BAQUFAAOCAQEAUYrMwJvGAcCAGnvYWKAiGHo5ee703br1cOLmeU48bNWanQyV
# B5F+9NduGCCYR+Dy/c6Qz0AAHOrfKZRMm9XVZjzR0SURkrw0XgUG+lUacr+buJk9
# soiQVq1JRSFVyzsjNTgUWRVHhIvvP9DYGG8ErZbn0b9CG4fkrmnP+K23Wdoz6PM1
# jzmLO50vGvU6WlBIVdDggAoWW4o8aomMZRdgmGxKPcNAVRVd6pvZz73GnTePE0Su
# d3zOUPMLoHd+DrNbb3tOwJhCCEIs2OMvQyZ7A6sS/YlTseBH5YefOj87+ZliRZCv
# hZJ/QldmfA3RI5Is2IKz45m0pmXUM9snjK0p6TCCBOswggPToAMCAQICEzMAAAF4
# JVq1zSPGX5UAAQAAAXgwDQYJKoZIhvcNAQEFBQAweTELMAkGA1UEBhMCVVMxEzAR
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjEjMCEGA1UEAxMaTWljcm9zb2Z0IENvZGUgU2ln
# bmluZyBQQ0EwHhcNMTcwODExMjAxMTE1WhcNMTgwODExMjAxMTE1WjCBgjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEMMAoGA1UECxMDQU9DMR4w
# HAYDVQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUA
# A4IBDwAwggEKAoIBAQCZbh1TVaudsrIbXUPPB9c8S+E+dKSbskHKYlG6SGTH8jhT
# hpuvGiAO87F2b9GHVN+DvszaMkGy/xQgHaGEJLfpnb3kpakic7E0bjDHdG4KnHRb
# no/wfUkGLfS79o+cw//RY8Ck6yE+0czDBcxp0Gbw5JyGP+KFqvzRR/3Tv3nt/5x0
# 5ZnEOHYP+eDVikDvn/DH+oxxtiCfX3tkvtm/yX4eOb47YdmYKQjCgz2+Nil/lupY
# vU0QFIjvke3jshqQINDng/vO9ys2qA0ex/q5hlLKQTST99dGoM86Ge6F723ReToq
# KnGLN8kiCG7uNapOAIQrpCHZq96CVumiaA5ZvxU9AgMBAAGjggFgMIIBXDATBgNV
# HSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUjuhtD3FD7tk/RKloJFX05cpgLjcw
# UQYDVR0RBEowSKRGMEQxDDAKBgNVBAsTA0FPQzE0MDIGA1UEBRMrMjI5ODAzKzFh
# YmY5ZTVmLWNlZDAtNDJlNi1hNjVkLWQ5MzUwOTU5ZmUwZTAfBgNVHSMEGDAWgBTL
# EejK0rQWWAHJNy4zFha5TJoKHzBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3Js
# Lm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNDb2RTaWdQQ0FfMDgt
# MzEtMjAxMC5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY0NvZFNpZ1BDQV8wOC0zMS0y
# MDEwLmNydDANBgkqhkiG9w0BAQUFAAOCAQEAYnG/oHG/xgZYR8NAMHZ/vE9GM0e4
# 7YdhuTea2uY7pSGwM707wp8Wan0Fa6evK1PWfcd/XNOh2BpEv5o8RmKDoEsG0ECP
# 13Jug7cklfKreBVHQ+Djg43VVFLZpuo7aOAVK6wjlcnpPUtn+SfH9K0aM2FjDKVJ
# FW6XFKXBat5R+Zp6uOxWTxpSeMTeDC5zF6IY6ogR1uzU+9EQoRlAvkwX6po+exEL
# nMLr4++P+fqOxIU+PODIoB8ijClAqwwKvLlMPa3qlrNHt+LweTMu7lvGC/RA18wU
# zzXAeomuZ03blUw+bkOiVgWOk4S0RN7EnW7zjJV8gd/+G2dbToUi1cB/fTCCBbww
# ggOkoAMCAQICCmEzJhoAAAAAADEwDQYJKoZIhvcNAQEFBQAwXzETMBEGCgmSJomT
# 8ixkARkWA2NvbTEZMBcGCgmSJomT8ixkARkWCW1pY3Jvc29mdDEtMCsGA1UEAxMk
# TWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTEwMDgzMTIy
# MTkzMloXDTIwMDgzMTIyMjkzMloweTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjEjMCEGA1UEAxMaTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0Ew
# ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCycllcGTBkvx2aYCAgQpl2
# U2w+G9ZvzMvx6mv+lxYQ4N86dIMaty+gMuz/3sJCTiPVcgDbNVcKicquIEn08Gis
# TUuNpb15S3GbRwfa/SXfnXWIz6pzRH/XgdvzvfI2pMlcRdyvrT3gKGiXGqelcnNW
# 8ReU5P01lHKg1nZfHndFg4U4FtBzWwW6Z1KNpbJpL9oZC/6SdCnidi9U3RQwWfjS
# jWL9y8lfRjFQuScT5EAwz3IpECgixzdOPaAyPZDNoTgGhVxOVoIoKgUyt0vXT2Pn
# 0i1i8UU956wIAPZGoZ7RW4wmU+h6qkryRs83PDietHdcpReejcsRj1Y8wawJXwPT
# AgMBAAGjggFeMIIBWjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTLEejK0rQW
# WAHJNy4zFha5TJoKHzALBgNVHQ8EBAMCAYYwEgYJKwYBBAGCNxUBBAUCAwEAATAj
# BgkrBgEEAYI3FQIEFgQU/dExTtMmipXhmGA7qDFvpjy82C0wGQYJKwYBBAGCNxQC
# BAweCgBTAHUAYgBDAEEwHwYDVR0jBBgwFoAUDqyCYEBWJ5flJRP8KuEKU5VZ5KQw
# UAYDVR0fBEkwRzBFoEOgQYY/aHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9j
# cmwvcHJvZHVjdHMvbWljcm9zb2Z0cm9vdGNlcnQuY3JsMFQGCCsGAQUFBwEBBEgw
# RjBEBggrBgEFBQcwAoY4aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0
# cy9NaWNyb3NvZnRSb290Q2VydC5jcnQwDQYJKoZIhvcNAQEFBQADggIBAFk5Pn8m
# Rq/rb0CxMrVq6w4vbqhJ9+tfde1MOy3XQ60L/svpLTGjI8x8UJiAIV2sPS9MuqKo
# VpzjcLu4tPh5tUly9z7qQX/K4QwXaculnCAt+gtQxFbNLeNK0rxw56gNogOlVuC4
# iktX8pVCnPHz7+7jhh80PLhWmvBTI4UqpIIck+KUBx3y4k74jKHK6BOlkU7IG9KP
# cpUqcW2bGvgc8FPWZ8wi/1wdzaKMvSeyeWNWRKJRzfnpo1hW3ZsCRUQvX/TartSC
# Mm78pJUT5Otp56miLL7IKxAOZY6Z2/Wi+hImCWU4lPF6H0q70eFW6NB4lhhcyTUW
# X92THUmOLb6tNEQc7hAVGgBd3TVbIc6YxwnuhQ6MT20OE049fClInHLR82zKwexw
# o1eSV32UjaAbSANa98+jZwp0pTbtLS8XyOZyNxL0b7E8Z4L5UrKNMxZlHg6K3RDe
# ZPRvzkbU0xfpecQEtNP7LN8fip6sCvsTJ0Ct5PnhqX9GuwdgR2VgQE6wQuxO7bN2
# edgKNAltHIAxH+IOVN3lofvlRxCtZJj/UBYufL8FIXrilUEnacOTj5XJjdibIa4N
# XJzwoq6GaIMMai27dmsAHZat8hZ79haDJLmIz2qoRzEvmtzjcT3XAH5iR9HOiMm4
# GPoOco3Boz2vAkBq/2mbluIQqBC0N1AI1sM9MIIGBzCCA++gAwIBAgIKYRZoNAAA
# AAAAHDANBgkqhkiG9w0BAQUFADBfMRMwEQYKCZImiZPyLGQBGRYDY29tMRkwFwYK
# CZImiZPyLGQBGRYJbWljcm9zb2Z0MS0wKwYDVQQDEyRNaWNyb3NvZnQgUm9vdCBD
# ZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDcwNDAzMTI1MzA5WhcNMjEwNDAzMTMw
# MzA5WjB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSEwHwYD
# VQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EwggEiMA0GCSqGSIb3DQEBAQUA
# A4IBDwAwggEKAoIBAQCfoWyx39tIkip8ay4Z4b3i48WZUSNQrc7dGE4kD+7Rp9FM
# rXQwIBHrB9VUlRVJlBtCkq6YXDAm2gBr6Hu97IkHD/cOBJjwicwfyzMkh53y9Gcc
# LPx754gd6udOo6HBI1PKjfpFzwnQXq/QsEIEovmmbJNn1yjcRlOwhtDlKEYuJ6yG
# T1VSDOQDLPtqkJAwbofzWTCd+n7Wl7PoIZd++NIT8wi3U21StEWQn0gASkdmEScp
# ZqiX5NMGgUqi+YSnEUcUCYKfhO1VeP4Bmh1QCIUAEDBG7bfeI0a7xC1Un68eeEEx
# d8yb3zuDk6FhArUdDbH895uyAc4iS1T/+QXDwiALAgMBAAGjggGrMIIBpzAPBgNV
# HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQjNPjZUkZwCu1A+3b7syuwwzWzDzALBgNV
# HQ8EBAMCAYYwEAYJKwYBBAGCNxUBBAMCAQAwgZgGA1UdIwSBkDCBjYAUDqyCYEBW
# J5flJRP8KuEKU5VZ5KShY6RhMF8xEzARBgoJkiaJk/IsZAEZFgNjb20xGTAXBgoJ
# kiaJk/IsZAEZFgltaWNyb3NvZnQxLTArBgNVBAMTJE1pY3Jvc29mdCBSb290IENl
# cnRpZmljYXRlIEF1dGhvcml0eYIQea0WoUqgpa1Mc1j0BxMuZTBQBgNVHR8ESTBH
# MEWgQ6BBhj9odHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0
# cy9taWNyb3NvZnRyb290Y2VydC5jcmwwVAYIKwYBBQUHAQEESDBGMEQGCCsGAQUF
# BzAChjhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY3Jvc29m
# dFJvb3RDZXJ0LmNydDATBgNVHSUEDDAKBggrBgEFBQcDCDANBgkqhkiG9w0BAQUF
# AAOCAgEAEJeKw1wDRDbd6bStd9vOeVFNAbEudHFbbQwTq86+e4+4LtQSooxtYrhX
# AstOIBNQmd16QOJXu69YmhzhHQGGrLt48ovQ7DsB7uK+jwoFyI1I4vBTFd1Pq5Lk
# 541q1YDB5pTyBi+FA+mRKiQicPv2/OR4mS4N9wficLwYTp2OawpylbihOZxnLcVR
# DupiXD8WmIsgP+IHGjL5zDFKdjE9K3ILyOpwPf+FChPfwgphjvDXuBfrTot/xTUr
# XqO/67x9C0J71FNyIe4wyrt4ZVxbARcKFA7S2hSY9Ty5ZlizLS/n+YWGzFFW6J1w
# lGysOUzU9nm/qhh6YinvopspNAZ3GmLJPR5tH4LwC8csu89Ds+X57H2146SodDW4
# TsVxIxImdgs8UoxxWkZDFLyzs7BNZ8ifQv+AeSGAnhUwZuhCEl4ayJ4iIdBD6Svp
# u/RIzCzU2DKATCYqSCRfWupW76bemZ3KOm+9gSd0BhHudiG/m4LBJ1S2sWo9iaF2
# YbRuoROmv6pH8BJv/YoybLL+31HIjCPJZr2dHYcSZAI9La9Zj7jkIeW1sMpjtHhU
# BdRBLlCslLCleKuzoJZ1GtmShxN1Ii8yqAhuoFuMJb+g74TKIdbrHk/Jmu5J4PcB
# ZW+JC33Iacjmbuqnl84xKf8OxVtc2E0bodj6L54/LlUWa8kTo/0xggSXMIIEkwIB
# ATCBkDB5MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSMwIQYD
# VQQDExpNaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQQITMwAAAXglWrXNI8ZflQAB
# AAABeDAJBgUrDgMCGgUAoIGwMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwG
# CisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMCMGCSqGSIb3DQEJBDEWBBQQfPvC
# lvHYL2+5g9lMlPnOoMaHIjBQBgorBgEEAYI3AgEMMUIwQKAWgBQAUABvAHcAZQBy
# AFMAaABlAGwAbKEmgCRodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vUG93ZXJTaGVs
# bCAwDQYJKoZIhvcNAQEBBQAEggEAMqlh/UzUoXirLupZLGr5ZILpl+il4qW5TSqe
# Rl/TiU4B03jnogIdZx+xFUzzfp/LoiT+0ImV0R/q30QiSwV4l6uU+2M/TNS4g41Q
# pTENag1d1h96iP5t5BMWHGypY02j//ROMRSeCUFkkRAu1k6+Pdo5QSmVbobhac0C
# ylVhu+7sMy/1YehFObiT/osGxWLKHBeotV+80vxFcVJ40gorhLHIRQSEctVpX7Ww
# 5X9QqZH6KQBuiyIukg9waLO4ijbxMGwr+b7LO2KTmjppHvCeCyG12Jsw7nuX/Vra
# O1XkUUWylVJ9zLSspd6evwHVrTZ/HSWpPHHtfW5CUKR/PQeDU6GCAigwggIkBgkq
# hkiG9w0BCQYxggIVMIICEQIBATCBjjB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMSEwHwYDVQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EC
# EzMAAAC5vA9OV+NmZTgAAAAAALkwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzEL
# BgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE3MTAxMDIwNDc0OFowIwYJKoZI
# hvcNAQkEMRYEFA1dgTlks9QiWTvbXrpBg/v1Mlb4MA0GCSqGSIb3DQEBBQUABIIB
# AAFK1SEt+tHbsdf/FqCpH8KhzHmnHABKsmBL3OjkJhHiK6t0vldH3IPTGMX+UwQz
# sZrqWes5cFUDUCVhSgZt/Ua8FNZLEwsrRQtbGTpoEN2VE+AdIW9sfOPK1wr+A22+
# tk2Qivh6Ibc18T3uGtF9RrzdLJeoBRpyHTD7xJnkQxYIefMayZbHjhuEelIybGWW
# V0Mu7MQGULJbJJkibYooL+UjZzRSPE7am4DlUP4dAnPFOygssYP80p3Am7OBH9vI
# BC8XSo1KtGLCjzEMNFTjRSgS4hpol9sOA7a/l94oeeLsAu7BaWVUQxZgynyMFS0s
# GkmOxDEd/MG0C0dO9obYynE=
# SIG # End signature block