Function HTTP_Resource_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_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) 04/19/15 dmb [SRPFW-95] Original programmer. 04/21/15 dmb [SRPFW-95] Fix bug in Prepare_Column_Values gosub that ignored calculated columns. Thanks to Barry Stevens for finding this. 04/21/15 dmb [SRPFW-95] Add PostDatabaseItem service. 04/21/15 dmb [SRPFW-95] Fix bug in Prepare_Column_Names gosub that ignored calculated columns. 04/21/15 dmb [SRPFW-95]Modify the GetDatabaseItems service to support multiple types of options in the Filter (formerly Query) argument. 05/04/15 dmb [SRPFW-95] Convert Prepare_Column_Names gosub into the GetColumnNames service. Retrofit all calls to the gosub to use the new service. 05/04/15 dmb [SRPFW-95] Convert Prepare_Column_Values gosub into the GetColumnValues service. Retrofit all calls to the gosub to use the new service. 05/04/15 dmb [SRPFW-95] Move the logic to exclude non-master columns and full Key ID columns into the GetColumnNames service. The GetColumnValues service will no longer be doing any column name filter. It will just work with the list provided. It will, however, still make sure the column name exists before attempting to get a value for it. 05/04/15 dmb [SRPFW-95] Modify the GetColumnValues service to return full Key IDs if the column name requires it. Add logic to decrement delimiters if @RM or @FM characters are found in the value. 05/04/15 dmb [SRPFW-95] Modify the PostDatabaseItem service to handle SRP_JSON PARSE errors better and return a 400 response status code. 05/15/15 dmb [SRPFW-95] Modify the GetDatabaseItems service to return an empty HAL+JSON embedded response if there are no matches. Remove the 404 status since the URL itself is valid although the database returns no matches. 06/09/15 dmb [SRPFW-95] Add ItemArrayLabel option for GetDatabaseItem and GetDatabaseItems services. This allows the calling service to customize the name of the primary HAL item array. 02/27/16 dmb [SRPFW-112] Update various services to use Memory_Services for caching. 03/23/16 dmb [SRPFW-114] Comment out the logic that attempted to remove full Key ID values from the response in the GetColumnValues services. Mark Boorman pointed out a bug in this logic, but the reason for doing this was unnecessary. 03/24/16 dmb [SRPFW-115] Add GetMVGroupNames service. 03/25/16 dmb [SRPFW-119] Update all SRP_JSON GETVALUE services used in the PostDatabaseItem service to use the Default argument to return an empty string if the objects value is null. 04/11/16 dmb [SRPFW-121] Update GetDatabaseItems service to use %RECORDS% if the Filter argument is empty. This will provide a sorted list of Key IDs automatically. 04/11/16 dmb [SRPFW-121] Update PostDatabaseItems service to use %RECORDS% if %SK% does not exist. This will make it easier to calculate the next available Key ID. 07/26/16 dmb [SRPFW-126] Refactor the GetColumnNames service and resolve a bug that Barry Steven's discovered where non-master data type columns would be returned. 10/13/16 dmb [SRPFW-129] Update the PostDatabaseItem service to set the response status to 201 when the ItemID is being passed in but it does not yet exist in the database. 07/01/17 dmb [SRPFW-184] Refactor using Enhanced BASIC+ syntax. 07/05/17 dmb Add PutDatabaseItem and PatchDatabaseItem services. Refactor so all DatabaseItem create and update services share relevant common code. 07/05/17 dmb Update several variables and comments to better match database terminology rather than HTTP terminology. 07/19/18 dmb Update the PostDatabaseItem, PutDatabaseItem, and PatchDatabaseItem services to support a new argument, AllowedColumnNames, which will be used to filter out column names which are submitted in the payload but should not be updated on the server. 11/29/18 dmb [SRPFW-259] Fix GetColumnNames to properly remove XREF columns. 11/29/18 dmb [SRPFW-259] Add GetObjects service. 11/29/18 dmb [SRPFW-259] Add GetSerializedResource service. 11/29/18 dmb [SRPFW-259] Add ParseResource service. 11/29/18 dmb [SRPFW-259] Add AddLinkRelationships service. 11/29/18 dmb [SRPFW-259] Add AddProperties service. 11/30/18 dmb [SRPFW-259] Add AddEmbeddedResources service. 11/30/18 dmb [SRPFW-259] Add AddNestedProperties service. 12/01/18 dmb [SRPFW-259] Update GetDatabaseItem to use the new resource services. 12/01/18 dmb [SRPFW-259] Update GetDatabaseItems to use the new resource services. 11/30/18 dmb [SRPFW-259] Add AddFormAction service. 12/02/18 dmb [SRPFW-259] Update GetColumnValues service so when a Key ID is missing it will set Error_Services and set the response status to 404. 12/04/18 dmb [SRPFW-259] Add support for multiple nested objects in the AddNestedProperties service. 12/06/18 dmb [SRPFW-257] Add LoremIpsum service. 12/12/18 dmb [SRPFW-259] Update GetObjects so that specific Key ID parts can be identified to be used in the self relationship URL and also add feature so the default '*' delimiter can be swapped out for another character. 01/03/19 dmb [SRPFW-259] Fix bug AddNestedProperties when using Array formatting. 02/14/19 dmb [SRPFW-259] Add IsArray argument for the AddProperties service so data can be automatically formatted as an array regardless if @VMs are found. This is meant to maintain formatting integrity of the property regardless of 0, 1, or multiple values. 03/04/19 dmb [SRPFW-259] Add GetObject service. Refactored GetObjects service to use GetObject service. 03/04/19 dmb [SRPFW-259] Add AddProperty service. Update AddProperties service to call AddProperty for each property. 03/04/19 dmb [SRPFW-259] Add AddSubProperty and AddSubProperties service. 03/04/19 dmb [SRPFW-259] Add AddSubResource and AddSubResources service. 03/05/19 dmb [SRPFW-259] Add AddSubResourceObject and AddSubResourceObjects service. 03/05/19 dmb [SRPFW-259] Remove AddNestedProperties service. Refit other services to use AddSubResources. 03/05/19 dmb [SRPFW-259] Add AddLinkRelationship service. Update AddLinkRelationships service to call AddLinkRelationship for each relationship. 03/05/19 dmb [SRPFW-259] Update all resource related services to use new services and remove direct calls to SRP_JSON except for the lowest level services. 03/05/19 dmb [SRPFW-259] Add AllowKeyProperty argument to the GetObject, GetObjects, and GetColumnNames services to override the default behavior of removing full Key ID columns from being represented in the object. This is useful for sub-resources. 03/05/19 dmb [SRPFW-259] Fix bug in GetObject service where associated property names were not being removed if the column names were removed by the GetColumnNames service. 03/06/19 dmb [SRPFW-259] Update the GetDatabaseItem and GetDatabaseItems service so the SelfURL argument is now optional. 04/10/19 dmb [SRPFW-271] Replace the Content-Location response header with Location in UpdateDatabaseRow. This is the correct header for 201 responses. (cf. https://tools.ietf.org/html/rfc2616#section-10.2.2) 05/28/19 dmb [SRPFW-274] Update the AddEmbeddedResources service to support a new Singular argument. This provides support for an embedded resource that is unique and should be represented as sub-properties rather than sub-resources. 05/28/19 dmb [SRPFW-274] Update the AddSubProperty service to correctly handle the SubPropertyValue argument if it contains an object handle. 05/28/19 dmb [SRPFW-274] Rename the GetLinkRelationship service to GetLinkRelation. Rename the GetLinkRelationships service to GetLinkRelations. 05/31/19 dmb [SRPFW-276] Update the GetColumnValues service so Error_Services is called after the SetResponseError call. This prevents Error_Services from being cleared prematurely. 05/31/19 dmb [SRPFW-276] Update the GetColumnNames service so Error_Services is called after the SetResponseError call. This prevents Error_Services from being cleared prematurely. 05/31/19 dmb [SRPFW-276] Update the GetMVGroupNames service so Error_Services is called after the SetResponseError call. This prevents Error_Services from being cleared prematurely. 12/09/19 dmb [SRPFW-296] Update all calls to Memory_Services to use a specific cache name. 12/16/19 dmb [SRPFW-296] Update the AddEmbeddedResources so it can be called more than once for the same embedded resource name. New resources will be added rather than replace what is already present. 01/22/20 dmb [SRPFW-296] Update the AddLinkRelation and AddLinkRelations services to support IsTemplate (IsTemplates) Boolean argument. This is to add support for URI Templates: https://tools.ietf.org/html/rfc6570 https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.2 01/26/20 dmb [SRPFW-296] Add GetURLTemplate service. 01/26/20 dmb [SRPFW-296] Update the AddLinkRelation service to call the GetURLTemplate service to create a URL Template out of an indicated expanded URL. 09/18/20 dmb [SRPFW-318] Update the AddSubProperty, AddSubResourceObject, and AddSubResource services to escape the property name to avoid problems with properties containing dots. ***********************************************************************************************************************/ #pragma precomp SRP_PreCompiler $insert LOGICAL $insert HTTP_INSERTS $insert SERVICE_SETUP $insert INET_EQUATES $insert INET_HEADERS $insert DICT_EQUATES Equ CRLF$ to \0D0A\ Equ CacheName$ to 'SRPHTTPFramework' Declare function HTTP_Resource_Services, HTTP_Resource_Manager_Services, Database_Services, Memory_Services Declare function SRP_Array Declare subroutine HTTP_Resource_Services, RList, Make.List, Activate_Save_Select, Memory_Services GoToService else HTTP_Services('SetResponseError', '', '', 404, Service : ' is not a valid service request within the HTTP Resource services module.') end Return Response OR '' //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Service Parameter Options //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// Options BOOLEAN = True$, False$ Options LAYOUT = 'Array', 'List' Options METHODS = 'GET', 'POST', 'PUT', 'PATCH', 'DELETE' Options STYLES = 'Fast', 'Styled', 'DropNulls' Options ALLDATATYPES = 'String', 'Number', 'Boolean', 'StringList', 'NumberList', 'BooleanList', 'ObjectHandle' //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Services //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //---------------------------------------------------------------------------------------------------------------------- // GetObject // // TableName - Name of the database table where the resource (database row) is stored. - [Optional] // KeyID - Key ID of a database row that should be used to build the resource. - [Optional] // ColumnNames - A delimited list of column names to build into the resource. Default will be all legitimate // columns which are returned by the GetColumnNames service. - [Optional] // PropertyNames - A delimited list of property names to use for each column. Default will be the column name. // - [Optional] // DataTypes - A delimited list of JSON data types (e.g., string, number, Boolean, etc.). Default will be // string. - [Optional] // MVGroupNames - A delimited list of MV group names that are to be matched with the delimited list of nested // group names. - [Optional] // NestedGroupNames - A delimited list of nested group names to use for each MV group name. This is only used if // a list of MV group names is also provided. - [Optional] // BaseSelfRelURL - The base URL to use for creating a self link relationship. If empty then no self link // relationship will be created. Otherwise, the Key ID will be appended as a segment to this // URL and added using the AddLinkRelations service. - [Optional] // KeyParts - A delimited list of Key ID parts that should be used to created the self link relationship. // Default is all Key ID parts will be used. - [Optional] // KeyDelimiter - The character to use as the delimiter for multi-part Key IDs when used in the self link // relationship. Default is '*', although this is not a URL friendly character. - [Optional] // AllowKeyProperty - A Boolean flag indicating that the resource object be allowed to represent the Key ID of the // database row to exists. This ultimately informs the GetColumnNames service to avoid stripping // out the Key ID column. - [Optional] // // Creates one or more new resource objects. The database table, Key ID, and list of column mames are provided, these // will be used to create properties within the resource object. //---------------------------------------------------------------------------------------------------------------------- Service GetObject(TableName, KeyID, ColumnNames, PropertyNames, DataTypes, MVGroupNames, NestedGroupNames, BaseSelfRelURL, KeyParts, KeyDelimiter, AllowKeyProperty) ObjectHandle = '' If (TableName NE '') AND (KeyID NE '') then FieldArray = Database_Services('ReadDataRow', 'DICT.' : TableName, '%FIELDS%', False$, 60) Convert @Lower_Case to @Upper_Case in ColumnNames Convert @VM : @SVM : @TM : @STM : ',' to @FM : @FM : @FM : @FM : @FM in ColumnNames Convert @VM : @SVM : @TM : @STM : ',' to @FM : @FM : @FM : @FM : @FM in PropertyNames Convert @VM : @SVM : @TM : @STM : ',' to @FM : @FM : @FM : @FM : @FM in DataTypes Convert @VM : @SVM : @TM : @STM : ',' to @FM : @FM : @FM : @FM : @FM in MVGroupNames Convert @VM : @SVM : @TM : @STM : ',' to @FM : @FM : @FM : @FM : @FM in NestedGroupNames Convert @VM : @SVM : @TM : @STM : ',' to @FM : @FM : @FM : @FM : @FM in KeyParts If KeyDelimiter EQ '' then KeyDelimiter = '*' MVColumnGroupNames = '' CompareColumnNames = ColumnNames ; // Save the original column names for comparison after they've been vetted. ColumnNames = HTTP_Resource_Services('GetColumnNames', TableName, ColumnNames, AllowKeyProperty) // Check each original column name against the vetted list. If it is missing, remove the associated property name // as well. NumColumnNames = DCount(CompareColumnNames, @FM) For ColumnPos = NumColumnNames to 1 Step -1 ColumnName = CompareColumnNames Locate ColumnName in ColumnNames using @FM setting fPos else PropertyNames = Delete(PropertyNames, ColumnPos, 0, 0) end Next ColumnPos hTable = Database_Services('GetTableHandle', TableName) If Error_Services('NoError') then If SRP_JSON(ObjectHandle, 'New', 'Object') then ColumnValues = HTTP_Resource_Services('GetColumnValues', TableName, ColumnNames, KeyID) If Error_Services('NoError') then Convert @VM : @SVM : @TM : @STM to @SVM : @TM : @STM : Char(249) in ColumnValues Convert @Lower_Case to @Upper_Case in ColumnNames For Each ColumnName in ColumnNames using @FM setting fPos PropertyName = PropertyNames If PropertyName EQ '' then PropertyNames = Oconv(ColumnName, 'MCL') DataType = DataTypes If DataType EQ '' then DataTypes = 'String' Locate ColumnName in FieldArray using @VM setting vPos then MVGroupName = FieldArray If MVGroupName NE '' then Locate MVGroupName in MVGroupNames using @FM setting Pos then NestedGroupName = NestedGroupNames If NestedGroupName EQ '' then NestedGroupName = Oconv(MVGroupName, 'MCL') MVColumnGroupNames = NestedGroupName end else MVColumnGroupNames = Oconv(MVGroupName, 'MCL') end end end Next ColumnName ColumnArray = ColumnNames : @RM : PropertyNames : @RM : DataTypes : @RM : MVColumnGroupNames : @RM : ColumnValues ColumnArray = SRP_Array('Rotate', ColumnArray, @RM, @FM) Convert @FM to @VM in ColumnArray Convert @RM to @FM in ColumnArray ColumnArray = SRP_Array('SortRows', ColumnArray, 'AL4', 'LIST') NestedValueArray = '' NestedPropertyNames = '' PrevMVColumnGroupName = '' For Each ColumnInfo in ColumnArray using @FM setting fPos ColumnName = ColumnInfo[1, @VM] PropertyName = ColumnInfo[Col2() + 1, @VM] DataType = ColumnInfo[Col2() + 1, @VM] MVColumnGroupName = ColumnInfo[Col2() + 1, @VM] Value = ColumnInfo[Col2() + 1, @VM] Convert @SVM : @TM : @STM : Char(249) to @VM : @SVM : @TM : @STM in Value If MVColumnGroupName NE '' then If (MVColumnGroupName NE PrevMVColumnGroupName) AND (PrevMVColumnGroupName NE '') then NestedPropertyNames[-1, 1] = '' NestedValueArray[-1, 1] = '' NestedValueArray = SRP_Array('Rotate', NestedValueArray, @FM, @VM) Convert @FM to @RM in NestedValueArray Convert @VM to @FM in NestedValueArray HTTP_Resource_Services('AddSubResources', ObjectHandle, PrevMVColumnGroupName, NestedPropertyNames, NestedValueArray) NestedPropertyNames = '' NestedValueArray = '' end NestedPropertyNames := PropertyName : @FM NestedValueArray := Value : @FM PrevMVColumnGroupName = MVColumnGroupName end else HTTP_Resource_Services('AddProperty', ObjectHandle, PropertyName, Value, DataType) end Next ColumnInfo If PrevMVColumnGroupName NE '' then NestedPropertyNames[-1, 1] = '' NestedValueArray[-1, 1] = '' NestedValueArray = SRP_Array('Rotate', NestedValueArray, @FM, @VM) Convert @FM to @RM in NestedValueArray Convert @VM to @FM in NestedValueArray HTTP_Resource_Services('AddSubResources', ObjectHandle, PrevMVColumnGroupName, NestedPropertyNames, NestedValueArray) end If BaseSelfRelURL NE '' then If KeyParts NE '' then NewKeyID = '' For Each KeyPart in KeyParts using @FM NewKeyID := Field(KeyID, '*', KeyPart, 1) : KeyDelimiter Next KeyPart NewKeyID[-1, 1] = '' Transfer NewKeyID to KeyID end else Convert '*' to KeyDelimiter in KeyID end SelfRelURL = BaseSelfRelURL : '/' : KeyID HTTP_Resource_Services('AddLinkRelations', ObjectHandle, 'self', SelfRelURL) end end end else Error_Services('Add', 'Error creating resource object in the ' : Service : ' service.') end end end else If SRP_JSON(ObjectHandle, 'New', 'Object') else Error_Services('Add', 'Error creating resource object in the ' : Service : ' service.') end end Response = ObjectHandle end service //---------------------------------------------------------------------------------------------------------------------- // GetObjects // // TableName - Name of the database table where the resource (database row) is stored. - [Optional] // Filter - Information that informs the service of how the database should be filtered. There are four // options: 1. Empty string, which is the default, meaning all database rows should be selected, // 2. A "SELECT" query that will be used in an RList() statement, 3. The name of a saved list of // Key IDs, or 4. An @FM delimited list of Key IDs. - [Optional] // ColumnNames - A delimited list of column names to build into the resource. Default will be all legitimate // columns which are returned by the GetColumnNames service. - [Optional] // PropertyNames - A delimited list of property names to use for each column. Default will be the column name. // - [Optional] // DataTypes - A delimited list of JSON data types (e.g., string, number, Boolean, etc.). Default will be // string. - [Optional] // MVGroupNames - A delimited list of MV group names that are to be matched with the delimited list of nested // group names. - [Optional] // NestedGroupNames - A delimited list of nested group names to use for each MV group name. This is only used if // a list of MV group names is also provided. - [Optional] // BaseSelfRelURL - The base URL to use for creating a self link relationship. If empty then no self link // relationship will be created. Otherwise, the Key ID will be appended as a segment to this // URL and added using the AddLinkRelations service. - [Optional] // KeyParts - A delimited list of Key ID parts that should be used to created the self link relationship. // Default is all Key ID parts will be used. - [Optional] // KeyDelimiter - The character to use as the delimiter for multi-park Key IDs when used in the self link // relationship. Default is '*', although this is not a URL friendly character. - [Optional] // AllowKeyProperty - A Boolean flag indicating that the resource object be allowed to represent the Key ID of the // database row to exists. This ultimately informs the GetColumnNames service to avoid stripping // out the Key ID column. - [Optional] // // Creates one or more new resource objects. The database table, Key ID, and list of column mames are provided, these // will be used to create properties within the resource object. //---------------------------------------------------------------------------------------------------------------------- Service GetObjects(TableName, Filter, ColumnNames, PropertyNames, DataTypes, MVGroupNames, NestedGroupNames, BaseSelfRelURL, KeyParts, KeyDelimiter, AllowKeyProperty) ObjectHandles = '' If TableName NE '' then hTable = Database_Services('GetTableHandle', TableName) If Error_Services('NoError') then Begin Case Case Filter EQ '' Filter = Xlate(TableName, '%RECORDS%', '', 'X') If Len(Filter) then rv = Set_Status(0) Make.List(0, Filter, '', '') end else Filter = 'SELECT ' : TableName : ' BY @ID' rv = Set_Status(0) RList(Filter, 5, '', '', '') end Case Filter[1, 7] EQ 'SELECT ' rv = Set_Status(0) RList(Filter, 5, '', '', '') Case Count(Filter, @FM) rv = Set_Status(0) Make.List(0, Filter, '', '') Case Otherwise$ // First, test to see if this is a valid saved list. If not, assume it is a single Key ID. If Num(Filter) then SaveList = Quote(Filter) end else SaveList = Filter end Activate_Save_Select(SaveList) If Get_Status() then rv = Set_Status(0) Make.List(0, Filter, '', '') end End Case EOF = False$ Loop Readnext KeyID else EOF = True$ Until EOF EQ True$ ObjectHandle = HTTP_Resource_Services('GetObject', TableName, KeyID, ColumnNames, PropertyNames, DataTypes, MVGroupNames, NestedGroupNames, BaseSelfRelURL, KeyParts, KeyDelimiter, AllowKeyProperty) If Error_Services('NoError') then ObjectHandles := ObjectHandle : @FM end else Error_Services('Add', 'Error creating resource object in the ' : Service : ' service.') end While Error_Services('NoError') Repeat ObjectHandles[-1, 1] = '' end end else ObjectHandles = HTTP_Resource_Services('GetObject') end Response = ObjectHandles end service //---------------------------------------------------------------------------------------------------------------------- // ParseResource // // SerializedResource - Name of the database table where the resource (database row) is stored. - [Optional] // // Creates a new resource object. If a database table, Key ID, and list of column mames are provided, these will be used // to create properties within the resource object. //---------------------------------------------------------------------------------------------------------------------- Service ParseResource(SerializedResource) ObjectHandle = '' If SerializedResource NE '' then If SRP_JSON(ObjectHandle, 'Parse', SerializedResource) EQ '' else Error_Services('Add', 'Error parsing the serialized resource in the ' : Service : ' service.') end end else Error_Services('Add', 'SerializedResource argument was missing from the ' : Service : ' service.') end Response = ObjectHandle end service //---------------------------------------------------------------------------------------------------------------------- // AddProperty // // ObjectHandle - Object handle to the resource. If empty, a new one will be created using the GetObject // service. - [Optional] // PropertyName - The property name being added. - [Required] // PropertyValue - The property value being added. Default will be an empty string. - [Optional] // DataType - The data type for the property value. These can be the standard JSON data types (e.g., 'String', // 'Number', or 'Boolean'). If the PropertyValue contains @VM delimiters, these will be formatted as // a JSON Array. There are a few special data types as well: // - 'StringList' - Same as 'String', but will force the values to be stored in a JSON Array even // if there are no @VM delimiters in the value. // - 'NumberList' - Same as 'Number', but will force the values to be stored in a JSON Array even // if there are no @VM delimiters in the value. // - 'BooleanList' - Same as 'Boolean', but will force the values to be stored in a JSON Array even // if there are no @VM delimiters in the value. // - 'ObjectHandle' - A JSON object handle. This is expected to be a sub-property or a sub-resource // object. This object handle will be released automatically. // If DataType is empty, the default will be 'String'. - [Optional] // // Adds a property to the indicated resource object along with any value provided. //---------------------------------------------------------------------------------------------------------------------- Service AddProperty(ObjectHandle, PropertyName, PropertyValue, DataType=ALLDATATYPES) If ObjectHandle EQ '' then ObjectHandle = HTTP_Resource_Services('GetObject') If (ObjectHandle NE '') AND (PropertyName NE '') then If DataType EQ '' then DataType = 'String' If DataType _EQC 'ObjectHandle' then If Num(PropertyValue) then Transfer PropertyValue to objProperty SRP_JSON(ObjectHandle, 'Set', PropertyName, objProperty) SRP_JSON(objProperty, 'Release') end else Error_Services('Add', 'The PropertyValue argument from the ' : Service : ' service does not contain a valid object handle.') end end else IsList = (Count(PropertyValue, @VM) GE 1) OR (DataType[-4, 4] _EQC 'List') If IsList EQ True$ then If SRP_JSON(arrayItem, 'New', 'Array') then For Each ItemValue in PropertyValue using @VM SRP_JSON(arrayItem, 'AddValue', ItemValue, DataType) Next ItemValue SRP_JSON(ObjectHandle, 'Set', PropertyName, arrayItem) SRP_JSON(arrayItem, 'Release') end end else SRP_JSON(ObjectHandle, 'SetValue', PropertyName, PropertyValue, DataType) end end end else Error_Services('Add', 'ObjectHandle or PropertyName argument was missing from the ' : Service : ' service.') end Response = ObjectHandle end service //---------------------------------------------------------------------------------------------------------------------- // AddProperties // // ObjectHandle - Object handle to the resource. If empty, a new one will be created using the GetObject // service. - [Optional] // PropertyNames - An @FM delimited list of one or more property names. - [Required] // PropertyValues - An @FM delimited list of property values being added. Default will be an empty string. // - [Optional] // DataTypes - An @FM delimited list of data types for the property values. See the DataType description for the // AddProperty service. - [Optional] // // Adds one or more properties in the indicated resource object. This is a wrapper for the AddProperty service to make // it easier to add multple properties to a resource object in one call. //---------------------------------------------------------------------------------------------------------------------- Service AddProperties(ObjectHandle, PropertyNames, PropertyValues, DataTypes) If ObjectHandle EQ '' then ObjectHandle = HTTP_Resource_Services('GetObject') If (ObjectHandle NE '') AND (PropertyNames NE '') then For Each PropertyName in PropertyNames using @FM setting fPos PropertyValue = PropertyValues DataType = DataTypes HTTP_Resource_Services('AddProperty', ObjectHandle, PropertyName, PropertyValue, DataType) Next PropertyName end else Error_Services('Add', 'ObjectHandle or PropertyNames argument was missing from the ' : Service : ' service.') end Response = ObjectHandle end service //---------------------------------------------------------------------------------------------------------------------- // AddSubProperty // // ObjectHandle - Object handle to the resource. If empty, a new one will be created using the GetObject // service. - [Optional] // PropertyName - The property name being added or updated. - [Required] // SubPropertyName - The sub-property name being added. - [Required] // SubPropertyValue - The sub-property value being added. Default will be an empty string. - [Optional] // DataType - The data type for the sub-property value. See the DataType description for the AddProperty // service. - [Optional] // // Adds a sub-property to the indicated property of the indicated resource object along with any value provided. This is // a one-service alternative to creating a temporary resource object and property (via GetObject with AddProperty) and // then adding this object to the primary resource as a property (via AddProperty using 'ObjectHandle' data type). //---------------------------------------------------------------------------------------------------------------------- Service AddSubProperty(ObjectHandle, PropertyName, SubPropertyName, SubPropertyValue, DataType=ALLDATATYPES) If ObjectHandle EQ '' then ObjectHandle = HTTP_Resource_Services('GetObject') If (ObjectHandle NE '') AND (PropertyName NE '') then If DataType EQ '' then DataType = 'String' Swap '.' with '..' in PropertyName ; // Escape the property name temporarily so the Get service will avoid treating it as a path. PropertyObjectHandle = SRP_JSON(ObjectHandle, 'Get', PropertyName) Swap '..' with '.' in PropertyName ; // Unescape the property name so it can be added to the JSON as intended. If PropertyObjectHandle EQ 0 then PropertyObjectHandle = '' ; // A 0 means the property doesn't exist in the primary resource. Make this empty so it will get created. PropertyObjectHandle = HTTP_Resource_Services('AddProperty', PropertyObjectHandle, SubPropertyName, SubPropertyValue, DataType) If Error_Services('NoError') then HTTP_Resource_Services('AddProperty', ObjectHandle, PropertyName, PropertyObjectHandle, 'ObjectHandle') end end else Error_Services('Add', 'ObjectHandle, PropertyName, or SubPropertyName argument was missing from the ' : Service : ' service.') end Response = ObjectHandle end service //---------------------------------------------------------------------------------------------------------------------- // AddSubProperties // // ObjectHandle - Object handle to the resource. If empty, a new one will be created using the GetObject // service. - [Optional] // PropertyName - The property name being added or updated. - [Required] // SubPropertyNames - An @FM delimited list of one or more sub-property names. - [Required] // SubPropertyValues - An @FM delimited list of sub-property values being added. Default will be an empty string. // - [Optional] // DataTypes - An @FM delimited list of data types for the sub-property values. See the DataType description for // the AddProperty service. - [Optional] // // Adds one or more sub-properties to the indicated property of the indicated resource object. This is a wrapper for the // AddSubProperty service to make it easier to add multple sub-properties to a property in one call. //---------------------------------------------------------------------------------------------------------------------- Service AddSubProperties(ObjectHandle, PropertyName, SubPropertyNames, SubPropertyValues, DataTypes) If ObjectHandle EQ '' then ObjectHandle = HTTP_Resource_Services('GetObject') If (ObjectHandle NE '') AND (SubPropertyNames NE '') then For Each SubPropertyName in SubPropertyNames using @FM setting fPos SubPropertyValue = SubPropertyValues DataType = DataTypes HTTP_Resource_Services('AddSubProperty', ObjectHandle, PropertyName, SubPropertyName, SubPropertyValue, DataType) Next PropertyName end else Error_Services('Add', 'ObjectHandle, PropertyName, or SubPropertyNames argument was missing from the ' : Service : ' service.') end Response = ObjectHandle end service //---------------------------------------------------------------------------------------------------------------------- // AddSubResourceObject // // ObjectHandle - Object handle to the resource. If empty, a new one will be created using the GetObject // service. - [Optional] // PropertyName - The property name being added or updated. - [Required] // SubResourceObjectHandle - The object handle to a sub-resource being added. - [Required] // // Adds a sub-resource object handle to the indicated property of the indicated resource object. Any pre-existing // sub-resources will be preserved and new ones will be appended. //---------------------------------------------------------------------------------------------------------------------- Service AddSubResourceObject(ObjectHandle, PropertyName, SubResourceObjectHandle) If ObjectHandle EQ '' then ObjectHandle = HTTP_Resource_Services('GetObject') If (ObjectHandle NE '') AND (PropertyName NE '') AND (SubResourceObjectHandle NE '') then Swap '.' with '..' in PropertyName ; // Escape the property name temporarily so the Get service will avoid treating it as a path. PropertyArrayHandle = SRP_JSON(ObjectHandle, 'Get', PropertyName) Swap '..' with '.' in PropertyName ; // Unescape the property name so it can be added to the JSON as intended. If PropertyArrayHandle EQ 0 then SRP_JSON(PropertyArrayHandle, 'New', 'Array') If PropertyArrayHandle GT 0 then SRP_JSON(PropertyArrayHandle, 'Add', SubResourceObjectHandle) SRP_JSON(SubResourceObjectHandle, 'Release') SRP_JSON(ObjectHandle, 'Set', PropertyName, PropertyArrayHandle) SRP_JSON(PropertyArrayHandle, 'Release') end else Error_Services('Add', 'Error creating property array handle in the ' : Service : ' service.') end end else Error_Services('Add', 'ObjectHandle, PropertyName, or SubResourceObjectHandle argument was missing from the ' : Service : ' service.') end Response = ObjectHandle end service //---------------------------------------------------------------------------------------------------------------------- // AddSubResourceObjects // // ObjectHandle - Object handle to the resource. If empty, a new one will be created using the GetObject // service. - [Optional] // PropertyName - The property name being added or updated. - [Required] // SubResourceObjectHandles - An @FM delimited list of sub-resource object handles being added. - [Required] // // Adds one or more sub-resource object handles to the indicated property of the indicated resource object. This is a // wrapper for the AddSubResourceObject service to make it easier to add multple sub-resource object handles to a // property in one call. //---------------------------------------------------------------------------------------------------------------------- Service AddSubResourceObjects(ObjectHandle, PropertyName, SubResourceObjectHandles) If ObjectHandle EQ '' then ObjectHandle = HTTP_Resource_Services('GetObject') If (ObjectHandle NE '') AND (PropertyName NE '') AND (SubResourceObjectHandles NE '') then For Each SubResourceObjectHandle in SubResourceObjectHandles using @FM HTTP_Resource_Services('AddSubResourceObject', ObjectHandle, PropertyName, SubResourceObjectHandle) Next SubResourceObjectHandle end else Error_Services('Add', 'ObjectHandle, PropertyName, or SubResourceObjectHandles argument was missing from the ' : Service : ' service.') end Response = ObjectHandle end service //---------------------------------------------------------------------------------------------------------------------- // AddSubResource // // ObjectHandle - Object handle to the resource. If empty, a new one will be created using the GetObject // service. - [Optional] // PropertyName - The property name being added or updated. - [Required] // SubResourcePropertyNames - An @FM delimited list of one or more sub-resource property names being added. // - [Required] // SubResourcePropertyValues - An @FM delimited list of one or more sub-resource property values being added. Default // will be an empty string. - [Optional] // DataTypes - An @FM delimited list of data types for the sub-resource property values. See the // DataType description for the AddProperty service. - [Optional] // // Adds a sub-resource to the indicated property of the indicated resource object. A sub-resource object handle is // created using the sub-resource property names and values and then added. Any pre-existing sub-resources will be // preserved and new ones will be appended. //---------------------------------------------------------------------------------------------------------------------- Service AddSubResource(ObjectHandle, PropertyName, SubResourcePropertyNames, SubResourcePropertyValues, DataTypes) If ObjectHandle EQ '' then ObjectHandle = HTTP_Resource_Services('GetObject') If (ObjectHandle NE '') AND (PropertyName NE '') AND (SubResourcePropertyNames NE '') then Swap '.' with '..' in PropertyName ; // Escape the property name temporarily so the Get service will avoid treating it as a path. PropertyArrayHandle = SRP_JSON(ObjectHandle, 'Get', PropertyName) Swap '..' with '.' in PropertyName ; // Unescape the property name so it can be added to the JSON as intended. If PropertyArrayHandle EQ 0 then SRP_JSON(PropertyArrayHandle, 'New', 'Array') If PropertyArrayHandle GT 0 then SubResourceObjectHandle = HTTP_Resource_Services('GetObject') If SubResourceObjectHandle GT 0 then For Each SubResourcePropertyName in SubResourcePropertyNames using @FM setting fPos SubResourcePropertyValue = SubResourcePropertyValues DataType = DataTypes HTTP_Resource_Services('AddProperty', SubResourceObjectHandle, SubResourcePropertyName, SubResourcePropertyValue, DataType) Next PropertyName HTTP_Resource_Services('AddSubResourceObject', ObjectHandle, PropertyName, SubResourceObjectHandle) end else Error_Services('Add', 'Error creating sub-resource object handle in the ' : Service : ' service.') end SRP_JSON(PropertyArrayHandle, 'Release') end else Error_Services('Add', 'Error creating property array handle in the ' : Service : ' service.') end end else Error_Services('Add', 'ObjectHandle, PropertyName, or SubResourcePropertyNames argument was missing from the ' : Service : ' service.') end Response = ObjectHandle end service //---------------------------------------------------------------------------------------------------------------------- // AddSubResources // // ObjectHandle - Object handle to the resource. If empty, a new one will be created using the // GetObject service. - [Optional] // PropertyName - The property name being added or updated. - [Required] // SubResourcePropertyNames - An @FM delimited list of one or more sub-resource property names being added. // - [Required] // SubResourcePropertyValuesArray - An @RM/@FM delimited array of one or more sub-resource property values being // added. Each @FM list will be treated as a sub-resource. Default will be an empty // string. - [Optional] // DataTypes - An @FM delimited list of data types for the sub-resource property values. See the // DataType description for the AddProperty service. - [Optional] // // Adds one or more sub-resources to the indicated property of the indicated resource object. This is a wrapper for the // AddSubResource service to make it easier to add multple sub-resources to a property in one call. //---------------------------------------------------------------------------------------------------------------------- Service AddSubResources(ObjectHandle, PropertyName, SubResourcePropertyNames, SubResourcePropertyValuesArray, DataTypes) If ObjectHandle EQ '' then ObjectHandle = HTTP_Resource_Services('GetObject') If (ObjectHandle NE '') AND (PropertyName NE '') AND (SubResourcePropertyNames NE '') then For Each SubResourcePropertyValues in SubResourcePropertyValuesArray using @RM HTTP_Resource_Services('AddSubResource', ObjectHandle, PropertyName, SubResourcePropertyNames, SubResourcePropertyValues, DataTypes) Next SubResourcePropertyValues end else Error_Services('Add', 'ObjectHandle, PropertyName, or SubResourcePropertyNames argument was missing from the ' : Service : ' service.') end Response = ObjectHandle end service //---------------------------------------------------------------------------------------------------------------------- // AddLinkRelation // // ObjectHandle - Object handle to the resource. - [Required] // Relation - The relation being added. - [Required] // URL - The URL associated to the indicated relation. - [Required] // Title - The friendly title for the indicated relation. - [Optional] // IsTemplate - Boolean flag to indicated if the link is URI Templated. - [Optional] // IgnoreResourceID - Boolean flag to determine if a resource ID segment should be left untemplated. - [Optional] // IgnoreProperty - Boolean flag to determine if a property segment should be left untemplated. - [Optional] // IgnoreQueryParam - Boolean flag to determine if existing query params should be left untemplated. - [Optional] // // Adds a _links relation to the indicated resource object. This is part of the HAL specification. //---------------------------------------------------------------------------------------------------------------------- Service AddLinkRelation(ObjectHandle, Relation, URL, Title, IsTemplate=BOOLEAN, IgnoreResourceID=BOOLEAN, IgnoreProperty=BOOLEAN, IgnoreQueryParam=BOOLEAN) If (ObjectHandle NE '') AND (Relation NE '') AND (URL NE '') then objRel = HTTP_Resource_Services('GetObject') If Error_Services('NoError') then If IsTemplate EQ True$ then URL = HTTP_Resource_Services('GetURLTemplate', URL, IgnoreResourceID, IgnoreProperty, IgnoreQueryParam) If Index(URL, '{', 1) then // The GetURLTemplate service was able to convert the URL to a URL Template. HTTP_Resource_Services('AddProperty', objRel, 'templated', True$, 'Boolean') end end HTTP_Resource_Services('AddProperty', objRel, 'href', URL) If Title NE '' then HTTP_Resource_Services('AddProperty', objRel, 'title', Title) end HTTP_Resource_Services('AddSubProperty', ObjectHandle, '_links', Relation, objRel, 'ObjectHandle') end end else Error_Services('Add', 'ObjectHandle, Relation, or URL argument was missing from the ' : Service : ' service.') end end service //---------------------------------------------------------------------------------------------------------------------- // AddLinkRelations // // ObjectHandle - Object handle to the resource. - [Required] // Relations - A delimited list of relations being added. - [Required] // URLs - A delimited list of URLs for each indicated relation. - [Required] // Titles - A delimited list of display friendly titles for each indicated relation. - [Optional] // IsTemplates - A delimited list of Boolean flags to indicated if the link is URI Templated. - [Optional] // IgnoreResourceIDs - Boolean flag to determine if a resource ID segment should be left untemplated. - [Optional] // IgnoreProperties - Boolean flag to determine if a property segment should be left untemplated. - [Optional] // IgnoreQueryParams - Boolean flag to determine if existing query params should be left untemplated. - [Optional] // // Adds one or more _links relations in the indicated resource object. This is part of the HAL specification. //---------------------------------------------------------------------------------------------------------------------- Service AddLinkRelations(ObjectHandle, Relations, URLs, Titles, IsTemplates, IgnoreResourceIDs=BOOLEAN, IgnoreProperties=BOOLEAN, IgnoreQueryParams=BOOLEAN) If (ObjectHandle NE '') AND (Relations NE '') AND (URLs NE '') then Convert @VM : @SVM : @TM : @STM : ',' to @FM : @FM : @FM : @FM : @FM in Relations Convert @VM : @SVM : @TM : @STM : ',' to @FM : @FM : @FM : @FM : @FM in URLs Convert @VM : @SVM : @TM : @STM : ',' to @FM : @FM : @FM : @FM : @FM in Titles Convert @VM : @SVM : @TM : @STM : ',' to @FM : @FM : @FM : @FM : @FM in IsTemplates For Each Relation in Relations using @FM setting fPos URL = URLs Title = Titles IsTemplate = IsTemplates IgnoreResourceID = IgnoreResourceIDs IgnoreProperty = IgnoreProperties IgnoreQueryParam = IgnoreQueryParams HTTP_Resource_Services('AddLinkRelation', ObjectHandle, Relation, URL, Title, IsTemplate, IgnoreResourceID, IgnoreProperty, IgnoreQueryParam) Next Name end else Error_Services('Add', 'ObjectHandle, Relations, or URLs argument was missing from the ' : Service : ' service.') end end service //---------------------------------------------------------------------------------------------------------------------- // AddEmbeddedResources // // ObjectHandle - Object handle to the resource. - [Required] // EmbeddedResourceName - Name of the embedded resource. - [Required] // EmbeddedResourceObjectHandles - An @FM delimited list of resource object handles being added. Each embedded // resource object handle will be released automatically. - [Required] // Singular - A Boolean flag indicating that the embedded resource object should be represented // as singular (i.e., unique) rather than as multiple. A singular embedded resource // object will be displayed using sub-properties but multiple embedded resources // (even if there is just one) will be displayed using sub-resources. The default // value is False. - [Optional] // // Adds one or more embedded resources in the indicated resource object. Embedded resources are stand-alone resources // on their own (and usually have their own endpoint), but are included, at least partially, in another resource due to // the close relationship between them. This is often done to eliminate multiple requests to pull in all significant // resource data. This is part of the HAL specification. //---------------------------------------------------------------------------------------------------------------------- Service AddEmbeddedResources(ObjectHandle, EmbeddedResourceName, EmbeddedResourceObjectHandles, Singular) If (ObjectHandle NE '') AND (EmbeddedResourceName NE '') AND (EmbeddedResourceObjectHandles NE '') then If Singular NE True$ then Singular = False$ If Singular EQ True$ then objEmbedded = HTTP_Resource_Services('AddSubProperty', '', EmbeddedResourceName, '', EmbeddedResourceObjectHandles, 'ObjectHandle') end else objEmbedded = SRP_JSON(ObjectHandle, 'Get', '_embedded') If objEmbedded EQ 0 then objEmbedded = '' objEmbedded = HTTP_Resource_Services('AddSubResourceObjects', objEmbedded, EmbeddedResourceName, EmbeddedResourceObjectHandles) end If Error_Services('NoError') then HTTP_Resource_Services('AddProperty', ObjectHandle, '_embedded', objEmbedded, 'ObjectHandle') end end else Error_Services('Add', 'ObjectHandle, EmbeddedResourceName, or EmbeddedResourceObjectHandles argument was missing from the ' : Service : ' service.') end end service //---------------------------------------------------------------------------------------------------------------------- // AddFormAction // // ObjectHandle - Object handle to the resource. - [Required] // Name - Name associated with this form action. - [Required] // Method - HTTP method used by this form action. - [Required] // URL - URL used as the target by this form action. - [Required] // Title - Display friendly title for this form action. Default is the Name argument. - [Optional] // Fields - A delimited list of fields that need to be submitted with this action. Fields can be interpreted // in any way the API chooses, but typically they map to a property of the current resource. // - [Optional] // FieldProperties - A structured @FM/@VM delimited array of property values associated with the fields. - [Optional] // For each field, the value structure follows this pattern: // = Default value. The value that should be passed in unless otherwise overridden by the // client. // = Required flag. A Boolean flag indicating if this property requires a value to be // passed in. // = Visible flag. A Boolean flag indicating if this property should be visible to the // client. A non-visible field simply means that the default value will automatically // be passed in. // // Adds one _forms action in the indicated resource object. Due to the complexity and depth of a single action, this // service will only add one action at a time. Additional actions can be added by calling this service multiple times. //---------------------------------------------------------------------------------------------------------------------- Service AddFormAction(ObjectHandle, Name, Method=METHODS, URL, Title, Fields, FieldProperties) If (ObjectHandle NE '') AND (Name NE '') AND (Method NE '') AND (URL NE '') then If Title EQ '' then Title = Name Convert @VM : @SVM : @TM : @STM : ',' to @FM : @FM : @FM : @FM : @FM in Fields objAction = HTTP_Resource_Services('GetObject') If Error_Services('NoError') then HTTP_Resource_Services('AddProperty', objAction, 'method', Method) HTTP_Resource_Services('AddProperty', objAction, 'action', URL) HTTP_Resource_Services('AddProperty', objAction, 'title', Title) objFields = HTTP_Resource_Services('GetObject') If Error_Services('NoError') then If Fields NE '' then For Each Field in Fields using @FM setting fPos HTTP_Resource_Services('AddSubProperty', objFields, Field, 'default', FieldProperties, 'String') HTTP_Resource_Services('AddSubProperty', objFields, Field, 'required', FieldProperties, 'Boolean') HTTP_Resource_Services('AddSubProperty', objFields, Field, 'visible', FieldProperties, 'Boolean') Next Field HTTP_Resource_Services('AddProperty', objAction, 'fields', objFields, 'ObjectHandle') end end HTTP_Resource_Services('AddSubProperty', ObjectHandle, '_forms', Name, objAction, 'ObjectHandle') end end else Error_Services('Add', 'ObjectHandle, Name, Method, or URL argument was missing from the ' : Service : ' service.') end end service //---------------------------------------------------------------------------------------------------------------------- // GetSerializedResource // // ObjectHandle - Object handle to the resource needing to be serialized. - [Required] // Style - Formatting style for the final output (Fast, DropNulls, or Styled). Default = Fast. - [Optional] // // Serializes the indicated resource object and then releases the object. //---------------------------------------------------------------------------------------------------------------------- Service GetSerializedResource(ObjectHandle, Style=STYLES) SerializedResource = '' If ObjectHandle NE '' then If Style EQ '' then Style = 'Fast' SerializedResource = SRP_JSON(ObjectHandle, 'Stringify', Style) SRP_JSON(ObjectHandle, 'Release') end else Error_Services('Add', 'ObjectHandle argument was missing from the ' : Service : ' service.') end Response = SerializedResource end service //---------------------------------------------------------------------------------------------------------------------- // LoremIpsum // // Creates a dummy HTTP response using lorem ipsum content. This is a placeholder service used when new endpoint // handlers are automatically created. It is expected to be replaced by the actual API logic. //---------------------------------------------------------------------------------------------------------------------- Service LoremIpsum() ObjectHandle = HTTP_Resource_Services('GetObjects') LoremIpsum = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ' LoremIpsum := 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure ' LoremIpsum := 'dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non ' LoremIpsum := 'proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' HTTP_Resource_Services('AddProperty', ObjectHandle, 'content', LoremIpsum, 'String') FullEndPointURL = HTTP_Services('GetFullEndPointURL') APIURL = HTTP_Services('GetAPIRootURL', True$) HTTP_Resource_Services('AddLinkRelations', ObjectHandle, 'self' : @FM : 'apiEntryPoint', FullEndpointURL : @FM : APIURL) jsonResource = HTTP_Resource_Services('GetSerializedResource', ObjectHandle, 'Fast') HTTP_Services('SetResponseBody', jsonResource, False$, 'application/hal+json') HTTP_Services('SetResponseStatus', 200) end service //---------------------------------------------------------------------------------------------------------------------- // GetURLTemplate // // URL - The URL that needs to be transformed into a URL template. - [Required] // IgnoreResourceID - Boolean flag to determine if a resource ID segment should be left untemplated. - [Optional] // IgnoreProperty - Boolean flag to determine if a property segment should be left untemplated. - [Optional] // IgnoreQueryParam - Boolean flag to determine if existing query params should be left untemplated. - [Optional] // // Returns the indicated URL as a URL Template. //---------------------------------------------------------------------------------------------------------------------- Service GetURLTemplate(URL, IgnoreResourceID=BOOLEAN, IgnoreProperty=BOOLEAN, IgnoreQueryParam=BOOLEAN) URLTemplate = '' If IgnoreResourceID NE True$ then IgnoreResourceID = False$ If IgnoreProperty NE True$ then IgnoreProperty = False$ If IgnoreQueryParam NE True$ then IgnoreQueryParam = False$ If URL NE '' then If Index(URL, '{', 1) then // The URL already appears to be formatted as a template. Return the URL that was passed in. URLTemplate = URL end else // The URL is not formatted as a template. Match it to a resource and create a templated URL. If URL[1, 1] EQ '/' then // URL is formatted as a relative URL. Start the URL Template as empty. URLTemplate = '' URL[1, 1] = '' end else // Assume the URL is absolute. Start the URL Template with the base URL. URLTemplate = Field(URL, '/', 1, 3) URL = Field(URL, '/', 4, 999) end If Index(URL, '?', 1) then // URL contains query params. Remove these for now and add them back only if IgnoreQueryParams is set // to True$. This way the original query params will be included in the URL Template. OrigQueryParams = Field(URL, '?', 2, 1) URL[-1, Neg(Len(OrigQueryParams) + 1)] = '' end else OrigQueryParams = '' end For Each Segment in URL using '/' URLTemplate := '/' : Segment ResourceClass = HTTP_Resource_Manager_Services('GetResourceProperty', URLTemplate, 'CLASS') Begin Case Case ResourceClass _EQC 'RESOURCE_ID' If IgnoreResourceID NE True$ then // This segment is a Resource ID. Get the NAME property for this URL and use this for the // segment instead. ResourceID = HTTP_Resource_Manager_Services('GetResourceProperty', URLTemplate, 'NAME') URLTemplate[-1, Neg(Len(Segment))] = ResourceID end Case ResourceClass _EQC 'PROPERTY' If IgnoreProperty NE True$ then // This segment is a Property. Use {propertyName} for the segment instead. URLTemplate[-1, Neg(Len(Segment))] = '{propertyName}' end End Case Next Segment If IgnoreQueryParam NE True$ then // Now the the URLTemplate is complete. Check to see if there are query params supported by this endpoint. QueryParams = HTTP_Resource_Manager_Services('GetResourceProperty', URLTemplate, 'QUERY_PARAMS') If QueryParams NE '' then QueryParams = '{?' : QueryParams : '}' URLTemplate := QueryParams end end else If OrigQueryParams NE '' then URLTemplate := '?' : OrigQueryParams end end end end else Error_Services('Add', 'URL argument was missing from the ' : Service : ' service.') end Response = URLTemplate end service //---------------------------------------------------------------------------------------------------------------------- // GetDatabaseItem // // TableName - Name of the database table where the resource (database row) is stored. - [Required] // SelfURL - The URL that identifies the item resource. - [Optional] // KeyID - Key ID for the database row. - [Required] // ColumnNames - An @FM delimited list of column names to build into the resource. If empty then all documented // column names from the %FIELDS% dictionary record will be used. - [Optional] // ItemArrayLabel - Label text to use when naming the primary HAL item array. If empty then "item" will be // used. - [Optional] // // Gets the resource item from a database table. The format will be HAL+JSON. //---------------------------------------------------------------------------------------------------------------------- Service GetDatabaseItem(TableName, SelfURL, KeyID, ColumnNames, ItemArrayLabel) BaseSelfURL = Field(SelfURL, '/', 1, Count(SelfURL, '/')) ObjectHandle = HTTP_Resource_Services('GetObjects', TableName, KeyID, ColumnNames, ItemArrayLabel) If SelfURL NE '' then HTTP_Resource_Services('AddLinkRelations', ObjectHandle, 'self' : @FM : 'collection', SelfURL : @FM : BaseSelfURL) end HAL = HTTP_Resource_Services('GetSerializedResource', ObjectHandle) HTTP_Services('SetResponseBody', HAL, False$, 'application/hal+json') // Use the Response variable for internal monitoring, tracking, and debugging. This does not affect the HTTP // response. Response = HAL end service //---------------------------------------------------------------------------------------------------------------------- // GetDatabaseItems // // Filter - Information that informs the service of how the database should be filtered. There are four // options: 1. Empty, if all database rows should be selected, 2. A "SELECT" query that will be used // in an RList() statement, 3. The name of a saved list of Key IDs, or 4. An @FM delimited list of // Key IDs. - [Optional] // TableName - Name of the database table where the resource (database row) is stored. - [Required] // SelfURL - The URL that identifies the collection resource. - [Optional] // ColumnNames - An @FM delimited list of column names to build into the resource. If empty then all documented // column names from the %FIELDS% dictionary record will be used. - [Optional] // ItemArrayLabel - Label text to use when naming the primary HAL item array. If empty then "item" will be // used. - (Optional) // // Gets the resource items from a database table. The format will be HAL+JSON. //---------------------------------------------------------------------------------------------------------------------- Service GetDatabaseItems(Filter, TableName, SelfURL, ColumnNames, ItemArrayLabel) HAL = '' ObjectHandle = HTTP_Resource_Services('GetObjects') If Error_Services('NoError') then objItems = HTTP_Resource_Services('GetObjects', TableName, Filter, ColumnNames, ItemArrayLabel, '', '', '', SelfURL) HTTP_Resource_Services('AddEmbeddedResources', ObjectHandle, 'item', objItems) If SelfURL NE '' then // Add _links sub-properties for HAL implementation. HTTP_Resource_Services('AddLinkRelations', ObjectHandle, 'self', SelfURL) end If Error_Services('NoError') then // Serialize the object into a JSON string. HAL = HTTP_Resource_Services('GetSerializedResource', ObjectHandle) // Set the response body with the JSON string and set the Content-Type response header. HTTP_Services('SetResponseBody', HAL, False$, 'application/hal+json') end end Response = HAL end service //---------------------------------------------------------------------------------------------------------------------- // DeleteDatabaseItem // // TableName - Name of the database table where the resource (database row) is stored. - [Required] // KeyID - Key ID for the database row. - [Required] // // Deletes the resource item from a database table. //---------------------------------------------------------------------------------------------------------------------- Service DeleteDatabaseItem(TableName, KeyID) If (TableName NE '') AND (KeyID NE '') then Open TableName to hTable then Lock hTable, KeyID then Read DataRow from hTable, KeyID then Delete hTable, KeyID then HTTP_Services('SetResponseStatus', 204) end else HTTP_Services('SetResponseError', '', '', 500, 'Error deleting ' : KeyID : ' from the ' : TableName : ' table.') end end else HTTP_Services('SetResponseStatus', 204) end Unlock hTable, KeyID else Null end else HTTP_Services('SetResponseError', '', '', 423, KeyID : ' is currently locked.') end end else // Unable to open the database table. Set a 500 status code. HTTP_Services('SetResponseError', '', '', 500, 'Unable to open the ' : TableName : ' table.') end end else // Required arguments are missing so just set a 404 status code. HTTP_Services('SetResponseError', '', '', 404, 'Required arguments are missing.') end end service //---------------------------------------------------------------------------------------------------------------------- // PutDatabaseItem // // TableName - Name of the database table where the resource (database row) is stored. - [Required] // SelfURL - The URL that identifies the resource. - [Required] // KeyID - Key ID for the database row. - [Required] // AllowedColumnNames - An @FM delimited list of column names that are permitted in this service. - [Optional] // // Creates a new or updates a specific resource item in the database table. To conform to the requirements of the PUT // method, the entire resource will be replaced with the content being passed in, even if only a few properties (e.g., // database columns) are being updated. Thus, it is assumed that both changed and unchanged properties will be passed // into the request body. //---------------------------------------------------------------------------------------------------------------------- Service PutDatabaseItem(TableName, SelfURL, KeyID, AllowedColumnNames) If (TableName NE '') AND (SelfURL NE '') AND (KeyID NE '') then Open TableName to hTable then HaveKeyID = False$ ; // Assume false for now. Lock hTable, KeyID then ItemURL = SelfURL Read OrigDataRow from hTable, KeyID then StatusCode = 200 ; // Updating an existing resource. end else StatusCode = 201 ; // Creating a new resource. end DataRow = '' ; // Clearing data row of all content. PUT assumes the entire resource is being updated. HaveKeyID = True$ end If HaveKeyID then GoSub UpdateDatabaseRow end else HTTP_Services('SetResponseError', '', '', 423, KeyID : ' is currently locked.') end end else HTTP_Services('SetResponseError', '', '', 500, 'Error opening the ' : TableName : ' table.') end end else // Required arguments are missing so just set a 404 status code. HTTP_Services('SetResponseError', '', '', 404, 'Required arguments are missing.') end end service //---------------------------------------------------------------------------------------------------------------------- // PostDatabaseItem // // TableName - Name of the database table where the resource (database row) is stored. - [Required] // SelfURL - The URL that identifies the resource. - [Required] // AllowedColumnNames - An @FM delimited list of column names that are permitted in this service. - [Optional] // // Creates a new resource item in the database table. To conform to the requirements of the POST method, the Key ID // will be created automatically by the server and the entire resource will be created with the content being passed in. //---------------------------------------------------------------------------------------------------------------------- Service PostDatabaseItem(TableName, SelfURL, AllowedColumnNames) If (TableName NE '') AND (SelfURL NE '') then Open TableName to hTable then HaveKeyID = False$ ; // Assume false for now. // Logic to create a new KeyID is placed here. The following code attempts to retrieve the KeyID from a // %SK% counter. If this fails, it will attempt to retrieve it from %RECORD%. Finally, if this fails then // KeyID will be set to a value of 1 and the code will increment this value until a new Key ID can be found. KeyID = Xlate('DICT.' : TableName, '%SK%', 1, 'X') If KeyID EQ '' then KeyIDs = Xlate(TableName, '%RECORDS%', '', 'X') KeyID = KeyIDs[-1, 'B' : @FM] KeyID += 1 end If KeyID EQ '' then KeyID = 1 Loop Lock hTable, KeyID then Read DataRow from hTable, KeyID then Unlock hTable, KeyID else Null end else OrigDataRow = '' DataRow = '' ItemURL = SelfURL : '/' : KeyID StatusCode = 201 ; // Creating a new resource. HaveKeyID = True$ end end Until HaveKeyID KeyID += 1 Repeat If HaveKeyID then GoSub UpdateDatabaseRow end else HTTP_Services('SetResponseError', '', '', 400, 'Error attempting to POST the resource to the ' : TableName : ' table.') end end else HTTP_Services('SetResponseError', '', '', 500, 'Error opening the ' : TableName : ' table.') end end else // Required arguments are missing so just set a 404 status code. HTTP_Services('SetResponseError', '', '', 404, 'Required arguments are missing.') end end service //---------------------------------------------------------------------------------------------------------------------- // PatchDatabaseItem // // TableName - Name of the database table where the resource (database row) is stored. - [Required] // SelfURL - The URL that identifies the resource. - [Required] // KeyID - Key ID for the database row. - [Required] // AllowedColumnNames - An @FM delimited list of column names that are permitted in this service. - [Optional] // // Updates a specific resource item in the database table. To conform to the requirements of the PATCH method, only // specific properties (e.g., database columns) of the resource will be updated. All properties not identified will // be left alone. //---------------------------------------------------------------------------------------------------------------------- Service PatchDatabaseItem(TableName, SelfURL, KeyID, AllowedColumnNames) If (TableName NE '') AND (SelfURL NE '') AND (KeyID NE '') then Open TableName to hTable then HaveKeyID = False$ ; // Assume false for now. Lock hTable, KeyID then ItemURL = SelfURL Read OrigDataRow from hTable, KeyID then DataRow = OrigDataRow StatusCode = 200 ; // Updating an existing resource. HaveKeyID = True$ end else StatusCode = 404 ; // Creating a new resource, which is unsupported for the PATCH method. end end If HaveKeyID then GoSub UpdateDatabaseRow end else HTTP_Services('SetResponseError', '', '', 423, KeyID : ' is currently locked.') end end else HTTP_Services('SetResponseError', '', '', 500, 'Error opening the ' : TableName : ' table.') end end else // Required arguments are missing so just set a 404 status code. HTTP_Services('SetResponseError', '', '', 404, 'Required arguments are missing.') end end service //---------------------------------------------------------------------------------------------------------------------- // GetColumnNames // // TableName - Name of the database table where the resource (database row) is stored. - [Required] // ColumnNames - An @FM delimited list of column names to build into the resource. If empty then all documented // column names from the %FIELDS% dictionary record will be used. - [Optional] // AllowKeyProperty - A Boolean flag indicating that the resource object be allowed to represent the Key ID of the // database row to exists. This ultimately informs the GetColumnNames service to avoid stripping // out the Key ID column. - [Optional] // // Gets the column names from the dictionary of the table provided that will be used to create the resource. If the // ColumnNames argument is used then this service will validate the list as valid columns. In either case, the following // rules will be applied: // // - Regardless of the case used in the ColumnNames argument it will be upper cased to match against the dictionary // names correctly. // - All column names bound to a Key ID of part 0 (i.e., the entire key) will be removed (unless AllowKeyProperty is // set to True). // - All non-master column names will be removed to avoid duplicates. // - SRP FrameWorks audit column names (MODIFIED_BY, MODIFIED_DATE, and MODIFIED_TIME) will be removed. //---------------------------------------------------------------------------------------------------------------------- Service GetColumnNames(TableName, ColumnNames, AllowKeyProperty) If AllowKeyProperty NE True$ then AllowKeyProperty = False$ // Make any column names passed in safe to work with. Convert @Lower_Case to @Upper_Case in ColumnNames Convert ',' to @FM in ColumnNames CompareColumnNames = ColumnNames // Create a service Key ID suitable for caching the results. ServiceKeyID := '*' : TableName If ColumnNames NE '' then ServiceKeyID := '*' : SRP_Hash(ColumnNames, 'SHA-1', 'BASE32') end // Check to see if column names have been cached this session. ColumnNames = Memory_Services('GetValue', ServiceKeyID, '', '', CacheName$) If ColumnNames EQ '' then If TableName NE '' then Open TableName to hTable then Open 'DICT.' : TableName to @DICT then Read Fields from @DICT, '%FIELDS%' else Fields = '' // Sort the fields array by descending Field Type. This puts Calculated (Type = 'S') columns before // Data (Type = 'F') columns. All Calculated columns qualify, so the list can stop when the // calculated columns are reached. Fields = SRP_Array('SortRows', Fields, 'DL' : FIELDS_TYPE$) FieldsList = SRP_Array('Rotate', Fields) // Restore the list of required column names, if any. Transfer CompareColumnNames to ColumnNames // Loop through the fields starting at the bottom and remove all fields that do not qualify. These // conditions disqualify a field: // - Full Key IDs - This allows Key Part columns to properly represent the data and abstract the // resource better. // - Non-master data type columns - This prevents duplication of data. // - Empty Column Names - This prevents special dictionary columns, like 'G' type from being // included. // - Cross-reference columns - These are calculated columns meant for indexing only. FieldPtr = DCount(FieldsList, @FM) Loop Until FieldPtr EQ 0 Master = Fields ; // Calculated columns have no Master flag. Type = Fields Conv = Fields MV = Fields Pos = Fields ColumnName = Fields KeyPart = Fields GroupName = Fields Indexed = Fields If (Master AND (Not(Pos EQ 0 AND KeyPart EQ 0) OR (AllowKeyProperty EQ True$))) OR ((Type _EQC 'S') AND (ColumnName[-5, 5] _NEC '_XREF')) then // The column must be a Master column (but not a full Key ID). If ColumnName EQ '' then // If the column does not have a name in the FIELDS_NAME$ value (for whatever reason) then // remove it from the array so it will be be included in the final results. Fields = Delete(Fields, FIELDS_FIELD_NO$, FieldPtr, 0) Fields = Delete(Fields, FIELDS_PART$, FieldPtr, 0) FieldsList = Delete(FieldsList, FieldPtr, 0, 0) end end else // The column does not meet the criteria. Remove the reference from the Fields array. Check // to see if this field was included in the ColumnNames argument and remove from that as // well. Fields = Delete(Fields, FIELDS_FIELD_NO$, FieldPtr, 0) Fields = Delete(Fields, FIELDS_PART$, FieldPtr, 0) FieldsList = Delete(FieldsList, FieldPtr, 0, 0) Locate ColumnName in ColumnNames using @FM setting fPos then ColumnNames = Delete(ColumnNames, fPos, 0, 0) end // Decrement the Field Position counter so the next item above in the array can be reviewed. FieldPtr -= 1 Repeat Fields = SRP_Array('Rotate', FieldsList) FieldNames = Fields Convert @VM to @FM in FieldNames If Len(ColumnNames) then // Check the supplied list of column names to make sure they are valid. CompareColumnNames = ColumnNames For Each ColumnName in CompareColumnNames using @FM Locate ColumnName in FieldNames using @FM setting fPos else Locate ColumnName in ColumnNames using @FM setting fPos then ColumnNames = Delete(ColumnNames, fPos, 0, 0) end end Next ColumnName end else ColumnNames = Fields Convert @VM to @FM in ColumnNames end // Exclude the audit column names in case they exist. Locate 'MODIFIED_BY' in ColumnNames using @FM setting fPos then ColumnNames = Delete(ColumnNames, fPos, 0, 0) Locate 'MODIFIED_DATE' in ColumnNames using @FM setting fPos then ColumnNames = Delete(ColumnNames, fPos, 0, 0) Locate 'MODIFIED_TIME' in ColumnNames using @FM setting fPos then ColumnNames = Delete(ColumnNames, fPos, 0, 0) If Len(ServiceKeyID) then Memory_Services('SetValue', ServiceKeyID, ColumnNames, CacheName$) end end else // Unable to open the dictionary table. Set a 500 status code. ErrorMessage = 'Unable to open the ' : TableName : ' dictionary table.' HTTP_Services('SetResponseError', '', '', 500, ErrorMessage) Error_Services('Add', ErrorMessage) end end else // Unable to open the database table. Set a 500 status code. ErrorMessage = 'Unable to open the ' : TableName : ' table.' HTTP_Services('SetResponseError', '', '', 500, ErrorMessage) Error_Services('Add', ErrorMessage) end end else // Required arguments are missing so just set a 404 status code. ErrorMessage = 'Required arguments are missing.' HTTP_Services('SetResponseError', '', '', 404, ErrorMessage) Error_Services('Add', ErrorMessage) end end Response = ColumnNames end service //---------------------------------------------------------------------------------------------------------------------- // GetColumnValues // // TableName - Name of the database table where the resource (database row) is stored. - [Required] // ColumnNames - An @FM delimited list of column names to build into the resource. - [Required] // KeyID - Key ID for the database row. - [Required] // // Gets the column values from the database row from the provided table and ID that will be used to create the resource. // This is returned as an @FM delimited list of values. //---------------------------------------------------------------------------------------------------------------------- Service GetColumnValues(TableName, ColumnNames, KeyID) ServiceKeyID := '*' : SRP_Hash(TableName : ColumnNames : KeyID, 'SHA-1', 'BASE32') ColumnValues = Memory_Services('GetValue', ServiceKeyID, True$, 10, CacheName$) If ColumnValues EQ '' then If (TableName NE '') AND (ColumnNames NE '') AND (KeyID NE '') then Open TableName to hTable then Open 'DICT.' : TableName to @DICT then Read Fields from @DICT, '%FIELDS%' else Fields = '' @ID = KeyID Read @RECORD from hTable, @ID then Convert @Lower_Case to @Upper_Case in ColumnNames NumColumns = Count(ColumnNames, @FM) + (ColumnNames NE '') If ColumnNames NE '' then For Each ColumnName in ColumnNames using @FM setting ColumnCnt ColumnName = ColumnNames ColumnValue = '' Locate ColumnName in Fields using @VM setting vPos then Master = Fields Type = Fields Conv = Fields MV = Fields Pos = Fields KeyPart = Fields GroupName = Fields // Now get the value based on factors like data vs. calculated and whole key vs. key part Begin Case Case Type EQ 'F' // Data column. If Len(KeyPart) then // This is a Key ID column. If KeyPart is 0 then this represents the entire // Key ID, which is not normally returned since the Item ID is already known // and will appear in the URL. If this is a multipart Key ID then KeyPart // will be an integer. This value should be returned since it likely is a // significant part of the resource. If KeyPart EQ 0 then ColumnValue = @ID end else ColumnValue = Field(@ID, '*', KeyPart, 1) end end else ColumnValue = @RECORD end Case Type EQ 'S' // Calculated (symbolic) column. If @RM or @FM delimiters are discovered then all // delimiters will be decremented as needed so the response payload will not become // out of sync. ColumnValue = Calculate(ColumnName) If Index(ColumnValue, @RM, 1) then Convert @STM to Char(248) in ColumnValue Convert @TM to Char(249) in ColumnValue Convert @SVM to @STM in ColumnValue Convert @VM to @TM in ColumnValue Convert @FM to @SVM in ColumnValue Convert @RM to @VM in ColumnValue end else If Index(ColumnValue, @FM, 1) then Convert @STM to Char(249) in ColumnValue Convert @TM to @STM in ColumnValue Convert @SVM to @TM in ColumnValue Convert @VM to @SVM in ColumnValue Convert @FM to @VM in ColumnValue end end End Case // If there is a conversion pattern then apply it. If Len(Conv) then ColumnValue = Oconv(ColumnValue, Conv) * If KeyPart EQ 0 then * ColumnNames = Delete(ColumnNames, ColumnCnt, 0, 0) * end else ColumnValues := ColumnValue : @FM * end end Next ColumnName end ColumnValues[-1, 1] = '' Memory_Services('SetValue', ServiceKeyID, ColumnValues, CacheName$) end else ErrorMessage = KeyID : ' is not a valid row in the ' : TableName : ' table.' HTTP_Services('SetResponseError', '', '', 404, ErrorMessage) Error_Services('Add', ErrorMessage) end end else // Unable to open the dictionary table. Set a 500 status code. ErrorMessage = 'Unable to open the ' : TableName : ' dictionary table.' HTTP_Services('SetResponseError', '', '', 500, ErrorMessage) Error_Services('Add', ErrorMessage) end end else // Unable to open the database table. Set a 500 status code. ErrorMessage = 'Unable to open the ' : TableName : ' table.' HTTP_Services('SetResponseError', '', '', 500, ErrorMessage) Error_Services('Add', ErrorMessage) end end else // Required arguments are missing so just set a 404 status code. ErrorMessage = 'Required arguments are missing.' HTTP_Services('SetResponseError', '', '', 404, ErrorMessage) Error_Services('Add', ErrorMessage) end end Response = ColumnValues end service //---------------------------------------------------------------------------------------------------------------------- // GetMVGroupNames // // TableName - Name of the database table. - [Required] // ColumnNames - An @FM delimited list of column names. - [Required] // // Gets the MV group names for the dictionary columns passed in. Note: the ColumnNames argument should be the same list // that was returned from the GetColumnNames service. This will return an @FM delimited list of MV group // names. Thus, for non-multivalue fields, the values will be empty. The MV group names will be returned in lower case. //---------------------------------------------------------------------------------------------------------------------- Service GetMVGroupNames(TableName, ColumnNames) ServiceKeyID := '*' : SRP_Hash(TableName : ColumnNames, 'SHA-1', 'BASE32') MVGroupNames = Memory_Services('GetValue', ServiceKeyID, '', '', CacheName$) If MVGroupNames EQ '' then If (TableName NE '') AND (ColumnNames NE '') then Open TableName to hTable then Open 'DICT.' : TableName to @DICT then Read Fields from @DICT, '%FIELDS%' else Fields = '' MVGroupNames = '' AllColumnNames = Fields AllGroupNames = Fields // Convert to upper case for now so that matches against the dictionary column names can be made. Convert @Lower_Case to @Upper_Case in ColumnNames // Loop through the fields and append the matching MV group names to the list. NumColumns = DCount(ColumnNames, @FM) For ColumnCnt = 1 to NumColumns ColumnName = ColumnNames Locate ColumnName in AllColumnNames using @VM setting vPos then MVGroupNames := AllGroupNames<0, vPos> : @FM Next ColumnCnt MVGroupNames[-1, 1] = '' Convert @Upper_Case to @Lower_Case in MVGroupNames Memory_Services('SetValue', ServiceKeyID, MVGroupNames, CacheName$) end else // Unable to open the dictionary table. Set a 500 status code. ErrorMessage = 'Unable to open the ' : TableName : ' dictionary table.' HTTP_Services('SetResponseError', '', '', 500, ErrorMessage) Error_Services('Add', ErrorMessage) end end else // Unable to open the database table. Set a 500 status code. ErrorMessage = 'Unable to open the ' : TableName : ' table.' HTTP_Services('SetResponseError', '', '', 500, ErrorMessage) Error_Services('Add', ErrorMessage) end end else // Required arguments are missing so just set a 404 status code. ErrorMessage = 'Required arguments are missing.' HTTP_Services('SetResponseError', '', '', 404, 'Required arguments are missing.') Error_Services('Add', ErrorMessage) end end Response = MVGroupNames end service //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Internal GoSubs //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //---------------------------------------------------------------------------------------------------------------------- // UpdateDatabaseRow // // Updates the database row with the data contained in the Request Body. This will write the updated row data to the // table and it will update the HTTP response status and headers as needed. This gosub is used by the PostDatabaseItem, // PutDatabaseItem, and PatchDatabaseItem services. //---------------------------------------------------------------------------------------------------------------------- UpdateDatabaseRow: // The new resource will have been put into the POST string. Body = HTTP_Services('GetHTTPPostString') // The POST string will have been encoded so use percent (URL) decoding. Body = HTTP_Services('DecodePercentString', Body) Convert @Lower_Case to @Upper_Case in AllowedColumnNames If Body EQ '' then ParseResponse = SRP_JSON(hBody, 'NEW', 'OBJECT') end else ParseResponse = SRP_JSON(hBody, 'PARSE', Body) end If (ParseResponse EQ '') OR (ParseResponse EQ 1) then // Go through the column names in the JSON object and map the column values to the respective // field positions in the database row. Fields = Xlate('DICT.' : TableName, '%FIELDS%', '', 'X') NumColumns = SRP_JSON(hBody, 'GETCOUNT') ColumnNames = SRP_JSON(hBody, 'GETMEMBERS') If ColumnNames NE '' then For Each ColumnName in ColumnNames using @FM setting ColumnCnt ColumnName = ColumnNames hColumn = SRP_JSON(hBody, 'GET', ColumnName) JSONType = SRP_JSON(hColumn, 'TYPE') If ColumnName[1, 1] NE '_' then // Ignore special properties which are for HATEOAS purpose. Begin Case Case JSONType _EQC 'Array' // Count the number of members in the array then loop through to build the @VM value list. ColumnValues = '' NumItems = SRP_JSON(hColumn, 'GETCOUNT') For ItemCnt = 1 to NumItems hItem = SRP_JSON(hColumn, 'GET', ItemCnt) ItemNames = SRP_JSON(hItem, 'GETMEMBERS') If ItemNames NE '' then For Each ColumnName in ItemNames using @FM setting ItemPtr ColumnValues = ColumnValues : SRP_JSON(hItem, 'GETVALUE', ColumnName, '') : @VM Next ItemName end SRP_JSON(hItem, 'Release') Next NumItems If ItemNames NE '' then For Each ColumnName in ItemNames using @FM setting ItemPtr ColumnValue = ColumnValues ColumnValue[-1, 1] = '' GoSub UpdateColumnData Next ColumnName end Case JSONType _EQC 'Object' // This is likely an MV group with one or more embedded arrays which need to be processed // individually. NumMVColumns = SRP_JSON(hColumn, 'GETCOUNT') MVColumnNames = SRP_JSON(hColumn, 'GETMEMBERS') If MVColumnNames NE '' then For Each ColumnName in MVColumnNames using @FM setting MVColumnCnt ColumnName = MVColumnNames hMVColumn = SRP_JSON(hColumn, 'GET', ColumnName) ColumnValue = '' NumValues = SRP_JSON(hMVColumn, 'GETCOUNT') For ValueCnt = 1 to NumValues ColumnValue := SRP_JSON(hMVColumn, 'GETVALUE', ColumnName : '[' : ValueCnt : ']', '') : @VM Next ValueCnt ColumnValue[-1, 1] = '' SRP_JSON(hMVColumn, 'RELEASE') GoSub UpdateColumnData Next ColumnName end Case Otherwise$ // This is just a regular column. Get the value. ColumnValue = SRP_JSON(hBody, 'GETVALUE', ColumnName, '') GoSub UpdateColumnData End Case end SRP_JSON(hColumn, 'RELEASE') Next ColumnName end Write DataRow to hTable, KeyID then // The database row has been successfully updated. Set the response status as originally identified by the // calling service. Set the Content-Location response header with the new or updated items URL so the // client can retrieve the resource if needed. HTTP_Services('SetResponseStatus', StatusCode) HTTP_Services('SetResponseHeaderField', 'Location', ItemURL) end else HTTP_Services('SetResponseError', '', '', 500, 'Error writing ' : KeyID : ' to the ' : TableName : ' table.') end end else HTTP_Services('SetResponseError', '', '', 400, ParseResponse) end Unlock hTable, KeyID else Null return //---------------------------------------------------------------------------------------------------------------------- // UpdateColumnData // // Updates the specified database column with the indicated value. This only updates the dynamic array and does not // write the data to the table. //---------------------------------------------------------------------------------------------------------------------- UpdateColumnData: Convert @Lower_Case to @Upper_Case in ColumnName AllowContinue = True$ ; // Assume this can continue for now. If AllowedColumnNames NE '' then Locate ColumnName in AllowedColumnNames using @FM setting AllowedFound else // This column is not approved for updating. AllowContinue = False$ end end Locate ColumnName in Fields using @VM setting vPos then Pos = Fields Type = Fields If Type EQ 'F' then If AllowContinue EQ True$ then Conv = Fields If Len(Conv) then ColumnValue = IConv(ColumnValue, Conv) DataRow = ColumnValue end else // This column is not allowed to be updated so make sure it retains the value stored in the database. DataRow = OrigDataRow end end end return