open-insight/LSL2/STPROC/SRP_EDITOR_TEMP_HTTP_RESOURCE_SERVICES_FRAMEWORKS.txt
Infineon\StieberD 7762b129af pre cutover push
2024-09-04 20:33:41 -07:00

1783 lines
106 KiB
Plaintext

Function SRP_EDITOR_TEMP_HTTP_RESOURCE_SERVICES_FRAMEWORKS(@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.
***********************************************************************************************************************/
#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<ColumnPos>
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<fPos>
If PropertyName EQ '' then PropertyNames<fPos> = Oconv(ColumnName, 'MCL')
DataType = DataTypes<fPos>
If DataType EQ '' then DataTypes<fPos> = 'String'
Locate ColumnName in FieldArray<FIELDS_NAME$> using @VM setting vPos then
MVGroupName = FieldArray<FIELDS_MV_GROUPNAME$, vPos>
If MVGroupName NE '' then
Locate MVGroupName in MVGroupNames using @FM setting Pos then
NestedGroupName = NestedGroupNames<Pos>
If NestedGroupName EQ '' then NestedGroupName = Oconv(MVGroupName, 'MCL')
MVColumnGroupNames<fPos> = NestedGroupName
end else
MVColumnGroupNames<fPos> = 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<fPos>
DataType = DataTypes<fPos>
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'
PropertyObjectHandle = SRP_JSON(ObjectHandle, 'Get', PropertyName)
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<fPos>
DataType = DataTypes<fPos>
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
PropertyArrayHandle = SRP_JSON(ObjectHandle, 'Get', PropertyName)
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
PropertyArrayHandle = SRP_JSON(ObjectHandle, 'Get', PropertyName)
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<fPos>
DataType = DataTypes<fPos>
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<fPos>
Title = Titles<fPos>
IsTemplate = IsTemplates<fPos>
IgnoreResourceID = IgnoreResourceIDs<fPos>
IgnoreProperty = IgnoreProperties<fPos>
IgnoreQueryParam = IgnoreQueryParams<fPos>
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:
// <x, 1> = Default value. The value that should be passed in unless otherwise overridden by the
// client.
// <x, 2> = Required flag. A Boolean flag indicating if this property requires a value to be
// passed in.
// <x, 3> = 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<fPos, 1>, 'String')
HTTP_Resource_Services('AddSubProperty', objFields, Field, 'required', FieldProperties<fPos, 2>, 'Boolean')
HTTP_Resource_Services('AddSubProperty', objFields, Field, 'visible', FieldProperties<fPos, 3>, '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<FIELDS_MASTER_FLAG$, FieldPtr> ; // Calculated columns have no Master flag.
Type = Fields<FIELDS_TYPE$, FieldPtr>
Conv = Fields<FIELDS_CONV$, FieldPtr>
MV = Fields<FIELDS_MVFLAG$, FieldPtr>
Pos = Fields<FIELDS_FIELD_NO$, FieldPtr>
ColumnName = Fields<FIELDS_NAME$, FieldPtr>
KeyPart = Fields<FIELDS_PART$, FieldPtr>
GroupName = Fields<FIELDS_MV_GROUPNAME$, FieldPtr>
Indexed = Fields<FIELDS_INDEX$, FieldPtr>
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<FIELDS_NAME$>
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<FIELDS_NAME$>
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<ColumnCnt>
ColumnValue = ''
Locate ColumnName in Fields<FIELDS_NAME$> using @VM setting vPos then
Master = Fields<FIELDS_MASTER_FLAG$, vPos>
Type = Fields<FIELDS_TYPE$, vPos>
Conv = Fields<FIELDS_CONV$, vPos>
MV = Fields<FIELDS_MVFLAG$, vPos>
Pos = Fields<FIELDS_FIELD_NO$, vPos>
KeyPart = Fields<FIELDS_PART$, vPos>
GroupName = Fields<FIELDS_MV_GROUPNAME$, vPos>
// 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<Pos>
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<FIELDS_NAME$>
AllGroupNames = Fields<FIELDS_MV_GROUPNAME$>
// 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<ColumnCnt>
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<ColumnCnt>
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<ItemPtr> = ColumnValues<ItemPtr> : 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<ItemPtr>
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<MVColumnCnt>
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<FIELDS_NAME$> using @VM setting vPos then
Pos = Fields<FIELDS_FIELD_NO$, vPos>
Type = Fields<FIELDS_TYPE$, vPos>
If Type EQ 'F' then
If AllowContinue EQ True$ then
Conv = Fields<FIELDS_CONV$, vPos>
If Len(Conv) then ColumnValue = IConv(ColumnValue, Conv)
DataRow<Pos> = ColumnValue
end else
// This column is not allowed to be updated so make sure it retains the value stored in the database.
DataRow<Pos> = OrigDataRow<Pos>
end
end
end
return