open-insight/FRAMEWORKS/STPROC/HTTP_RESOURCE_MANAGER_SERVICES.txt
2024-03-25 15:15:48 -07:00

583 lines
30 KiB
Plaintext

Function HTTP_Resource_Manager_Services(@Service, @Params)
/***********************************************************************************************************************
This program is proprietary and is not to be used by or disclosed to others, nor is it to be copied without written
permission from SRP Computer Solutions, Inc.
Name : HTTP_Resource_Manager_Services
Description : Handler program for all module related services.
Notes : The generic parameters should contain all the necessary information to process the services. Often
this will be information like the data Record and Key ID.
Parameters :
Service [in] -- Name of the service being requested
Param1-10 [in/out] -- Additional request parameter holders
Response [out] -- Response to be sent back to the Controller (MCP) or requesting procedure
Metadata :
History : (Date, Initials, Notes)
07/12/19 dmb [SRPFW-277] Original programmer.
07/13/19 dmb [SRPFW-277] Added various services so that the NDW_HTTP_FRAMEWORKS_SERVICES commuter as well
as additional tools can get or set various resource meta data.
07/15/19 dmb [SRPFW-277] Added the GetEndpointResourceKeyID service.
07/16/19 dmb [SRPFW-277] Added the GetResourceSignature service.
07/16/19 dmb [SRPFW-277] Update GetResourceProperty service to support the RESOURCE property.
07/16/19 dmb [SRPFW-277] Replaced harcoded cache duration with the CacheTTL$ equate.
07/16/19 dmb [SRPFW-277] Rename the IsResource service to IsValidEndpoint.
07/17/19 dmb [SRPFW-277] Update the SetResource service to refresh the GetResource cache better.
07/24/19 dmb [SRPFW-278] Fix bug in the GetEndpointResourceKeyID service so that matching the URLEndpoint
works better.
09/25/19 dmb [SRPFW-278] Fix bug in the GetResource service that prevented endpoints with a resource ID
from being matched correctly to the currently stored resources endpoints.
12/09/19 dmb [SRPFW-296] Update all calls to Memory_Services to use a specific cache name.
01/23/20 dmb [SRPFW-296] Update the SetResourceProperty and GetResourceSignature services to support the
HEAD method.
***********************************************************************************************************************/
#pragma precomp SRP_PreCompiler
$insert LOGICAL
$insert HTTP_INSERTS
$insert SERVICE_SETUP
$insert INET_EQUATES
$insert INET_HEADERS
$insert HTTP_FRAMEWORK_SETUP_EQUATES
Equ CRLF$ to \0D0A\
Equ CacheTTL$ to 300 ; // Allow cached data to only be fresh for 5 seconds.
Equ CacheName$ to 'SRPHTTPFramework'
Equ ValidMethods$ to 'GET,POST,PUT,PATCH,DELETE,HEAD'
Declare function HTTP_Resource_Manager_Services, HTTP_Resource_Services, Database_Services, Memory_Services, SRP_Array
Declare subroutine HTTP_Resource_Manager_Services, HTTP_Resource_Services, Database_Services, Memory_Services
GoToService else
HTTP_Services('SetResponseError', '', '', 404, Service : ' is not a valid service request within the HTTP Resource Manager services module.')
end
Return Response OR ''
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Service Parameter Options
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Options BOOLEAN = True$, False$
Options CLASSES = 'APIROOT', 'RESOURCE', 'RESOURCE_ID', 'PROPERTY'
Options PROPERTIES = 'CLASS', 'DESCRIPTION', 'EXCLUDE_LOGGING', 'METHODS', 'NAME', 'RESOURCE', 'TITLE', 'QUERY_PARAMS'
Options CHILDREN = 'ALL', 'RESOURCE', 'RESOURCE_ID', 'PROPERTY'
Options METHODS = 'GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD'
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Services
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------------------------------------------------
// GetResourceProperty
//
// URLEndpoint - URL endpoint for the resource. If empty, the APIROOT resource will be returned. - [Optional]
// PropertyName - Name of the property whose value is being queried. - [Required]
//
// Returns the value of the indicated resource property for the indicated URL endpoint.
//----------------------------------------------------------------------------------------------------------------------
Service GetResourceProperty(URLEndpoint, PropertyName=PROPERTIES)
PropertyValue = ''
If PropertyName NE '' then
Resource = HTTP_Resource_Manager_Services('GetResource', URLEndpoint)
If Error_Services('NoError') then
PropertyNames = Resource<1>
Locate PropertyName in PropertyNames using @VM setting PropertyPos then
PropertyValue = Resource<2, PropertyPos>
end
Begin Case
Case PropertyName EQ 'EXCLUDE_LOGGING'
// This property defaults to false unless true.
If PropertyValue NE True$ then PropertyValue = False$
Case PropertyName EQ 'RESOURCE' AND PropertyValue = ''
// Unless the system has forced this property to store a value, it should be derived from the
// resource signature.
Signature = HTTP_Resource_Manager_Services('GetResourceSignature', URLEndpoint)
PropertyValue = Signature[1, '.']
End Case
end
end else
Error_Services('Add', 'PropertyName argument was missing in the ' : Service : ' service.')
end
Response = PropertyValue
end service
//----------------------------------------------------------------------------------------------------------------------
// SetResourceProperty
//
// URLEndpoint - URL endpoint for the resource. If empty, the APIROOT resource will be returned. - [Optional]
// PropertyName - Name of the property whose value is being set. - [Required]
// PropertyValue - Value of the property being updated. Depending upon the property, this might clear the value or
// set a default value. - [Optional]
//
// Sets (or updates) the value of the indicated resource property for the indicated URL endpoint.
//----------------------------------------------------------------------------------------------------------------------
Service SetResourceProperty(URLEndpoint, PropertyName=PROPERTIES, PropertyValue)
If PropertyName NE '' then
Resource = HTTP_Resource_Manager_Services('GetResource', URLEndpoint)
If Error_Services('NoError') then
PropertyNames = Resource<1>
Begin Case
Case PropertyName EQ 'METHODS'
// Make sure only valid HTTP methods are set.
Transfer PropertyValue to Methods
For Each Method in Methods using ',' setting cPos
Locate Method in ValidMethods$ using ',' setting MethodPos then
PropertyValue := Method : ','
end
Next Method
PropertyValue[-1, 1] = ''
Case PropertyName EQ 'EXCLUDE_LOGGING'
// This property defaults to false unless true.
If PropertyValue NE True$ then PropertyValue = False$
Case PropertyName EQ 'CLASS'
// Make sure only a valid resource class is set.
Locate PropertyValue in 'RESOURCE,RESOURCE_ID,PROPERTY' using ',' setting ClassPos else
PropertyValue = 'RESOURCE'
end
End Case
Locate PropertyName in PropertyNames using @VM setting PropertyPos else
Resource<1, PropertyPos> = PropertyName
end
Resource<2, PropertyPos> = PropertyValue
HTTP_Resource_Manager_Services('SetResource', URLEndpoint, Resource)
end
end else
Error_Services('Add', 'PropertyName argument was missing in the ' : Service : ' service.')
end
end service
//----------------------------------------------------------------------------------------------------------------------
// GetResourceChildren
//
// URLEndpoint - URL endpoint for the resource. If empty, the APIROOT resource will be returned. - [Optional]
// ChildType - Type of child resource being queried. If empty or ALL then all children are returned. - [Optional]
//
// Returns an @FM delimited list of URL endpoints matching the indicated child type that are children to the indicated
// URL endpoint.
//----------------------------------------------------------------------------------------------------------------------
Service GetResourceChildren(URLEndpoint, ChildType=CHILDREN)
Children = ''
ResourceList = HTTP_Resource_Manager_Services('GetResourceList')
If Error_Services('NoError') then
// Loop through the resource list to find resource children.
If ResourceList NE '' then
Locate ChildType in 'ALL,RESOURCE,RESOURCE_ID,PROPERTY' using ',' setting ChildPos else
ChildType = 'ALL'
end
ThisEndpointKeyID = HTTP_Resource_Manager_Services('GetEndpointResourceKeyID', URLEndpoint)
ThisLevel = DCount(ThisEndpoint, '/')
For Each ResourceItem in ResourceList using @FM
Endpoint = ResourceItem<0, 2>
EndpointLevel = DCount(Endpoint, '/')
If EndpointLevel EQ 2 then
EndpointParent = 'APIROOT'
end else
EndpointParent = Field(Endpoint, '/', 1, EndpointLevel - 1)
end
If EndpointParent _EQC ThisEndpointKeyID then
EndpointType = ResourceItem<0, 5>
If (EndpointType EQ ChildType) OR (ChildType EQ 'ALL') then
Children := Endpoint : @FM
end
end
Next ResourceItem
end
end
Children[-1, 1] = ''
Response = Children
end service
//----------------------------------------------------------------------------------------------------------------------
// GetResourceSignature
//
// URLEndpoint - URL endpoint for the resource. If empty, the APIROOT resource will be returned. - [Optional]
// Method - HTTP method for the signature. This must be a valid method but it can also be empty. If empty,
// the signature returned will simply omit the method. - [Optional]
//
// Returns the API calling signature for the indicated URL endpoint. This is used by the RunWebAPI service (a member of
// HTTP_Services) when calling the Web API module.
//----------------------------------------------------------------------------------------------------------------------
Service GetResourceSignature(URLEndpoint, Method=METHODS)
Signature = ''
URLEndpointKeyID = HTTP_Resource_Manager_Services('GetEndpointResourceKeyID', URLEndpoint)
// Validate the HTTP method.
Locate Method in ValidMethods$ using ',' setting MethodPos else
Method = ''
end
If URLEndpointKeyID EQ 'APIROOT' then
Signature = 'APIROOT.'
end else
ThisLevel = DCount(URLEndpointKeyID, '/')
For LevelCnt = 2 to ThisLevel
Class = HTTP_Resource_Manager_Services('GetResourceProperty', Field(URLEndpointKeyID, '/', 1, LevelCnt), 'CLASS')
Begin Case
Case Class EQ 'RESOURCE'
// A resource starts a new signature.
Signature = Field(URLEndpointKeyID, '/', LevelCnt, 1) : '.'
Case Class EQ 'RESOURCE_ID'
Signature := 'ID.'
Case Otherwise$
Signature := Field(URLEndpointKeyID, '/', LevelCnt, 1) : '.'
End Case
Next LevelCnt
end
If Method NE '' then
Signature := Method
end else
Signature[-1, 1] = ''
end
Response = Signature
end service
//----------------------------------------------------------------------------------------------------------------------
// GetEndpointResourceKeyID
//
// URLEndpoint - URL endpoint to be validated. If empty, the APIROOT resource will be returned. - [Optional]
// AllowNew - Boolean flag indicating if a URL endpoint can be returned if new. Default is false. - [Optional]
//
// Returns the resource Key ID for the indicated URL endpoint. If AllowNew is true, a URL endpoint will be returned that
// best matches the available endpoints but it will not be cached.
//----------------------------------------------------------------------------------------------------------------------
Service GetEndpointResourceKeyID(URLEndpoint, AllowNew)
ServiceKeyID := '*' : URLEndpoint
ResourceKeyID = Memory_Services('GetValue', ServiceKeyID, True$, CacheTTL$, CacheName$)
ResourceKeyIDFound = True$ ; // Assume true for now.
If AllowNew NE True$ then AllowNew = False$
IsNew = False$ ; // Assume false for now.
If ResourceKeyID EQ '' then
If (URLEndpoint EQ '') OR (URLEndpoint _EQC 'APIROOT') then
ResourceKeyID = 'APIROOT'
end else
// Make sure the URL endpoint is well formed. The intent is for the incoming URL endpoint to be forgiving but
// internally it needs to match with the exact format of the endpoints used to uniquely identify each resource.
Swap 'https' with '' in URLEndpoint
Swap 'http' with '' in URLEndpoint
HomeURL = HTTP_Services('GetHomeURL')
Swap 'https' with '' in HomeURL
Swap 'http' with '' in HomeURL
APIURL = HTTP_Services('GetAPIRootURL', False$)
If URLEndpoint[-1, 1] EQ '/' then URLEndpoint[-1, 1] = ''
// Remove references to the Home or API URLs since these are not used when creating the resource endpoint
// identifiers.
Swap HomeURL with '' in URLEndpoint
Swap APIURL with '' in URLEndpoint
If URLEndpoint EQ '' then
// An empty URL endpoint implies APIROOT.
ResourceKeyID = 'APIROOT'
end else
// The URL endpoint needs to be walked one segment at a time to confirm that it matches the pattern of
// an existing resource endpoint. Since the true URL endpoint might contains a resource ID, there needs
// to be a check to see if the value of a given segment matches a defined resource or resource property
// first. If not, then confirm there is a resource ID defined for this segment.
ResourceList = HTTP_Resource_Manager_Services('GetResourceList')
ResourceArray = SRP_Array('Rotate', ResourceList, @FM , @VM)
ResourceEndpoints = ResourceArray<2>
ResourceNames = ResourceArray<3>
ResourceClasses = ResourceArray<5>
// Since the stored resource endpoints contain user defined resource IDs, each of these need to be
// converted to a generic '{ResourceID}' so simple comparison logic can work.
For Each Class in ResourceClasses using @VM setting ClassPos
If Class EQ 'RESOURCE_ID' then
OrigResourceEndpoint = ResourceEndpoints<0, ClassPos>
ResourceEndpoint = OrigResourceEndpoint
ResourceName = ResourceNames<0, ClassPos>
Swap ResourceName with '{ResourceID}' in ResourceEndpoint
Swap OrigResourceEndpoint with ResourceEndpoint in ResourceEndpoints
end
Next Class
Convert @Upper_Case to @Lower_Case in ResourceEndpoints
Convert @Upper_Case to @Lower_Case in URLEndpoint
// Remove any preceding "/" characters so the For Each will work
// better.
If URLEndpoint[1, 1] EQ '/' then URLEndpoint = URLEndpoint[2, 9999]
MatchResourceKeyID = ''
// Walk the URL endpoint provided and check for matches. Build the resource Key ID along the way. If a
// given segment is unable to be matched to a defined resource endpoint, end the loop and clear the
// resource Key ID.
FinalSegment = False$
For Each Segment in URLEndpoint using '/' setting SegmentPos
MatchResourceKeyID := '/' : Segment
Locate MatchResourceKeyID in ResourceEndpoints using @VM setting URLPos then
If Segment[1, 1] EQ '{' then
ResourceKeyID := '/{resourceid}'
end else
ResourceKeyID := '/' : Segment
end
end else
MatchResourceKeyID = Field(MatchResourceKeyID, '/', 1, SegmentPos) : '/{resourceid}'
Locate MatchResourceKeyID in ResourceEndpoints using @VM setting URLPos then
ResourceKeyID := '/{resourceid}'
end else
If AllowNew EQ True$ then
ResourceKeyID := '/' : Segment
IsNew = True$
end else
FinalSegment = True$
end
end
end
While (ResourceKeyIDFound EQ True$) AND (FinalSegment EQ False$)
Next Segment
end
end
If ResourceKeyIDFound EQ True$ then
If IsNew NE True$ then
Memory_Services('SetValue', ServiceKeyID, ResourceKeyID, CacheName$)
end
end else
ResourceKeyID = ''
end
end
Response = ResourceKeyID
end service
//----------------------------------------------------------------------------------------------------------------------
// GetResource
//
// URLEndpoint - URL endpoint for the resource. If empty, the APIROOT resource will be returned. - [Optional]
//
// Returns an array of resource property names and values for the indicated URL endpoint.
//----------------------------------------------------------------------------------------------------------------------
Service GetResource(URLEndpoint)
URLEndpointKeyID = HTTP_Resource_Manager_Services('GetEndpointResourceKeyID', URLEndpoint)
ServiceKeyID := '*' : URLEndpointKeyID
Resource = Memory_Services('GetValue', ServiceKeyID, True$, CacheTTL$, CacheName$)
If Resource EQ '' then
ResourceList = HTTP_Resource_Manager_Services('GetResourceList')
If Error_Services('NoError') then
// The URL endpoint needs to be walked one segment at a time to confirm that it matches the pattern of
// an existing resource endpoint. Since the true URL endpoint might contains a resource ID, there needs
// to be a check to see if the value of a given segment matches a defined resource or resource property
// first. If not, then confirm there is a resource ID defined for this segment.
ResourceArray = SRP_Array('Rotate', ResourceList, @FM , @VM)
ResourceEndpoints = ResourceArray<2>
ResourceNames = ResourceArray<3>
ResourceClasses = ResourceArray<5>
// Since the stored resource endpoints contain user defined resource IDs, each of these need to be
// converted to a generic '{ResourceID}' so simple comparison logic can work.
For Each Class in ResourceClasses using @VM setting ClassPos
If Class EQ 'RESOURCE_ID' then
OrigResourceEndpoint = ResourceEndpoints<0, ClassPos>
ResourceEndpoint = OrigResourceEndpoint
ResourceName = ResourceNames<0, ClassPos>
Swap ResourceName with '{ResourceID}' in ResourceEndpoint
Swap OrigResourceEndpoint with ResourceEndpoint in ResourceEndpoints
end
Next Class
Convert @Upper_Case to @Lower_Case in ResourceEndpoints
MatchURLEndpointKeyID = URLEndpointKeyID
Convert @Upper_Case to @Lower_Case in MatchURLEndpointKeyID
Locate MatchURLEndpointKeyID in ResourceEndpoints using @VM setting ResourcePos then
ResourceArray = ResourceList<ResourcePos>
Convert @VM to @FM in ResourceArray
NameProperty = ResourceArray<3>
ClassProperty = ResourceArray<5>
OtherProperties = ResourceArray<19>
Convert @SVM to @FM in OtherProperties
Convert @TM to @VM in OtherProperties
ResourceList = 'NAME' : @VM : NameProperty : @FM : 'CLASS' : @VM : ClassProperty : @FM : OtherProperties
Resource = SRP_Array('Rotate', ResourceList, @FM, @VM)
Memory_Services('SetValue', ServiceKeyID, Resource, CacheName$)
end
end
end
Response = Resource
end service
//----------------------------------------------------------------------------------------------------------------------
// SetResource
//
// URLEndpoint - URL endpoint for the resource. If empty, the APIROOT resource will be returned. - [Optional]
// PropertyArray - An @FM/@VM delimited array of resource property names and values for the resource. - [Required]
//
// Updates the array of resource property names and values for the indicated URL endpoint. Note, this replaces the
// existing property names and values. Other services should be used to update an existing array and then call this
// service when the array is updated. The PropertyArray must have at least one property name and value.
//----------------------------------------------------------------------------------------------------------------------
Service SetResource(URLEndpoint, PropertyArray)
If PropertyArray NE '' then
URLEndpointKeyID = HTTP_Resource_Manager_Services('GetEndpointResourceKeyID', URLEndpoint, True$)
ResourceList = HTTP_Resource_Manager_Services('GetResourceList')
If Error_Services('NoError') then
ResourceArray = SRP_Array('Rotate', ResourceList, @FM , @VM)
MatchURLEndpointKeyID = URLEndpointKeyID
Convert @Upper_Case to @Lower_Case in MatchURLEndpointKeyID
ResourceEndpoints = ResourceArray<2>
Convert @Upper_Case to @Lower_Case in ResourceEndpoints
Locate MatchURLEndpointKeyID in ResourceEndpoints By 'AL' using @VM setting ResourcePos then
// This is an existing resource so update it.
Resource = ResourceList<ResourcePos>
Convert @VM to @FM in Resource
end else
// This is a new resource.
ResourceList = Insert(ResourceList, ResourcePos, 0, 0, '')
GoSub GetResourceTemplate
end
// Update the resource array with the property array data.
PropertyNames = PropertyArray<1>
PropertyValues = PropertyArray<2>
Locate 'NAME' in PropertyNames using @VM setting PropertyPos then
Name = PropertyValues<0, PropertyPos>
PropertyNames = Delete(PropertyNames, 0, PropertyPos, 0)
PropertyValues = Delete(PropertyValues, 0, PropertyPos, 0)
end else
// If no Name is provided, use the final segment of the URL endpoint.
Name = URLEndpointKeyID[-1, 'B/']
PropertyArray = Insert(PropertyArray, 1, 1, 0, 'NAME')
PropertyArray = Insert(PropertyArray, 2, 1, 0, Name)
end
Locate 'CLASS' in PropertyNames using @VM setting PropertyPos then
Class = PropertyValues<0, PropertyPos>
Locate Class in 'RESOURCE,RESOURCE_ID,PROPERTY' using ',' setting ClassPos else
Class = 'RESOURCE'
PropertyArray<2, PropertyPos> = Class
end
PropertyNames = Delete(PropertyNames, 0, PropertyPos, 0)
PropertyValues = Delete(PropertyValues, 0, PropertyPos, 0)
end else
// All resources are required to have a class. If none is specified then set it to 'RESOURCE'.
Class = 'RESOURCE'
PropertyArray = Insert(PropertyArray, 1, 1, 0, 'CLASS')
PropertyArray = Insert(PropertyArray, 2, 1, 0, Class)
end
Memory_Services('SetValue', ServiceModule : '*GetResource*' : URLEndpointKeyID, PropertyArray, CacheName$)
Resource<03> = Name
Resource<05> = Class
OtherProperties = PropertyNames : @FM : PropertyValues
OtherPropertiesList = SRP_Array('Rotate', OtherProperties, @FM, @VM)
Convert @VM to @TM in OtherPropertiesList
Convert @FM to @SVM in OtherPropertiesList
Resource<19> = OtherPropertiesList
Convert @FM to @VM in Resource
ResourceList<ResourcePos> = Resource
ResourcesKeyID = HTTP_Services('GetLocalAppKeyID', ResourcesKeyID$)
Database_Services('WriteDataRow', SetupTable$, ResourcesKeyID, ResourceList, True$, False$, True$)
end
end else
Error_Services('Add', 'PropertyArray was missing from the ' : Service : ' service.')
end
end service
//----------------------------------------------------------------------------------------------------------------------
// IsValidEndpoint
//
// URLEndpoint - URL endpoint for the resource. If empty, a False value will be returned. - [Required]
//
// Returns a Boolean flag indicating if the indicated URL endpoint exists.
//----------------------------------------------------------------------------------------------------------------------
Service IsValidEndpoint(URLEndpoint)
IsValidEndpoint = False$ ; // Assume False for now.
If URLEndpoint NE '' then
URLEndpointKeyID = HTTP_Resource_Manager_Services('GetEndpointResourceKeyID', URLEndpoint)
If URLEndpointKeyID NE '' then IsValidEndpoint = True$
end else
Error_Services('Add', 'URLEndpoint argument was missing in the ' : Service : ' service.')
end
Response = IsValidEndpoint
end service
//----------------------------------------------------------------------------------------------------------------------
// GetResourceList
//
// Returns the resource list for the current application.
//----------------------------------------------------------------------------------------------------------------------
Service GetResourceList()
ResourceList = ''
ResourcesKeyID = HTTP_Services('GetLocalAppKeyID', ResourcesKeyID$)
If Error_Services('NoError') then
ResourceList = Database_Services('ReadDataRow', SetupTable$, ResourcesKeyID, True$, CacheTTL$, False$)
end
Response = ResourceList
end service
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Internal GoSubs
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------------------------------------------------
// GetResourceTemplate
//
// Returns a template for a new resource. Most of this information is needed for the NDW_HTTP_FRAMEWORK_SETUP form's
// Tree control.
//----------------------------------------------------------------------------------------------------------------------
GetResourceTemplate:
Resource = ''
Resource<01> = DCount(URLEndpointKeyID, '/')
Resource<02> = URLEndpointKeyID
Resource<03> = URLEndpointKeyID[-1, 'B/']
Resource<04> = 'Text' : @SVM
Resource<05> = ''
Resource<06> = ''
Resource<07> = 'RGB{68, 68, 68}' : @SVM : 'White'
Resource<08> = 'Segoe UI' : @SVM : '-12' : @SVM : '400' : @SVM : '0' : @SVM : '0' : @SVM : '0' : @SVM : '1' : @SVM : '0' : @SVM : '0' : @SVM : '0' : @SVM : '0' : @SVM : '0'
Resource<09> = 'Left' : @SVM : 'Center'
Resource<10> = 'N'
Resource<11> = 'None'
Resource<12> = 1
Resource<13> = 24
Resource<14> = 0
Resource<15> = 1
Resource<16> = 'None'
Resource<17> = 'Left'
Resource<18> = 13 : @SVM : 13
return