Custom data support in Platina API
Background
Platina is a highly customizable product that exists on the market for years. During the long period, Platina customers implemented large number of customizations by using various approaches including basic ones, like using single predefined custom fields provided by Platina, but also non-trivial which involve custom database tables, functions, procedures and UI forms with custom controls. Since Platina exposes part of its functionality through the API, custom data support becomes one of the major and prioritized features that allows to integrate with other external systems that customers may use.
The purpose of this article is to describe how custom data for Platina objects can be defined and managed via Platina API. This article also includes an information of how to make custom data for existing non-trivial customizations being supported by Platina API.
Custom data in Platina
Platina supports few different ways to define and manage custom data, however please be aware that some of the mentioned below options are not supported by Platina API yet:
- Use
bk-fieldsthat can accept single value. Amount of such fields are limited and have predefined names, e.g.bkStrNN,bkIntNN,bkDateNN,bkFloatNN, whereNNis an integer value in range 1-30 or 1-10 depending on field and object type. Such fields are capable to store custom data of typestring,integer,dateorfloatrespectively. - [NOT supported by Platina API yet] Use
EF-fields(extended fields) that could store a single value of string type. Such fields have custom defined names, which should always be prefixed withEF_. Platina supports any amount of fields of that kind per object while they have a unique name (however it is worth to mention, that the more fields are defined for object, the more it could affect the Platina performance in a bad way). - Define custom data tables and stored procedures in
platina_custom(or other) database. Implement custom business logic and UI (this approach is usually used in non-trivial customization scenarios). - Store arbitrary custom data for object in JSON format (currently supported only by Platina API).
Managing custom data using Platina API
bk-fields
bk-fields storage and retrieval is supported by Platina API out of the box. All that is required, is to send a valid request to the corresponding endpoint that handles particular object metadata create, read or update. For example, for journalized cases such endpoints are:
- POST
/api/v7/diaries/{id}/journalized-casesto create a journalized case including custom bk-fields. - GET
/api/v7/journalized-cases/{id}to read the object metadata including custom bk-fields. - PATCH
/api/v7/journalized-cases/{id}to update the object metadata including custom bk-fields.
This is the simplest option available to specify a basic custom data for object. One of the additional benefits for using bk-fields is that data, specified there, becomes automatically searchable in Platina.
Reference data
There could be cases when external applications (API clients) require some predefined data being returned from Platina and which is usually available for all authenticated users and can be used as the data source for dropdown or list fields on UI. Such data may include list of Countries, supported PUL and Secrecy levels, Case or Deed Types, whatever. Platina API already exposes endpoints that can be requested for standard reference data as mentioned before, or are capable to return custom data of such kind.
There are two general endpoints that can be requested for getting a list of defined standard or custom Platina reference data:
- GET
/api/v7/reference-datafor standard reference data. - GET
/api/v7/reference-data/customfor custom reference data.
Both return response in the same format which includes the following JSON properties:
hrefURL to endpoint that returns specific reference data.relidentifier of the reference data to return.methodHTTP method required to request the reference data.
Example response for standard reference data request:
{
"_links": [
{
"href": "https://api-server/api/v7/reference-data/countries",
"rel": "countries",
"method": "GET"
},
...
]
}
Example response for custom reference data request:
{
"_links": [
{
"href": "https://api-server/api/v7/reference-data/custom/CustomDocumentPrefixes",
"rel": "CustomDocumentPrefixes",
"method": "GET"
},
...
]
}
To expose custom reference data, Platina API uses custom views with special RefData_ prefix that should be created in platina_custom database as part of Platina customization process. The view name that follows RefData_ prefix would be treated by Platina API as an identifier of custom reference data.
For example, if user makes HTTP
GETrequest tohttps://api-server/api/v7/reference-data/custom/CustomDocumentPrefixesendpoint, then Platina API searches for view namedRefData_CustomDocumentPrefixes, and if view is found, queries data from it and returns result JSON format.
Implementation example of custom reference data for Platina API:
We have a custom data table named tDocumentPrefixes that stores custom prefixes for documents. For this example, the table structure and data is the following:
| ID | Prefix |
|---|---|
| 1 | DEV |
| 2 | REQ |
| 3 | QA |
| 4 | PM |
The task is to expose data from that table via Platina API as reference data. We decided to use CustomDocumentPrefixes as reference data identifier. The next step would be to create a new view named RefData_CustomDocumentPrefixes in platina_custom database:
USE platina_custom
GO
CREATE VIEW RefData_CustomDocumentPrefixes
AS
SELECT [id], [prefix] FROM tDocumentPrefixes
GO
When the view is created, the HTTP GET request to https://api-server/api/v7/reference-data/custom/CustomDocumentPrefixes should return the data from the created view:
[
{
"id": 1,
"prefix": "DEV"
},
{
"id": 2,
"prefix": "REQ"
},
{
"id": 3,
"prefix": "QA"
},
{
"id": 4,
"prefix": "PM"
}
]
Implementation example of custom reference data for Platina API with multi-language support:
In some cases, reference data should be returned considering the preferred language setting for requesting user. It is also supported by Platina API. To have a multi-language support, the RefData_ view should include langID column that stores a language identifier for each row (e.g. 1 for Swedish, 2 for English languages, etc.).
To demonstrate such scenario, let's expose the Platina's standard DISCARD_REASON hierarchy register values by custom identifier DiscardReasons via Platina API. Implementation is quite simple - just create the following view named RefData_DiscardReasons in platina_custom database:
USE platina_custom
GO
CREATE VIEW RefData_DiscardReasons
AS
SELECT reg.[registerID], reglang.[text], reglang.[langID]
FROM platina.dbo.tHierarchyRegister reg
INNER JOIN platina.dbo.tHierarchyRegisterLanguage reglang
ON reg.[registerID] = reglang.[registerID]
WHERE reg.[registerName] = 'DISCARD_REASON'
If select data from the created view, it should look like follows:
| registerID | text | langID |
|---|---|---|
| 26001 | Dubbelregistrerat | 1 |
| 26001 | Double registered | 2 |
| 26001 | Dobbeltregistrering | 3 |
| 26001 | Dobbelregistrert | 4 |
| 26002 | Felregistrerat | 1 |
| 26002 | Wrongly registered | 2 |
| 26002 | Fejlregistrering | 3 |
| 26002 | Feilregistrert | 4 |
| 26003 | Testdata | 1 |
| 26003 | Test data | 2 |
| 26003 | Testdata | 3 |
| 26003 | Testdata | 4 |
Make an HTTP GET request to https://api-server/api/v7/reference-data/custom/DiscardReasons endpoint. Depending on the selected active language for the Platina user, which account is used by Platina API, the response may look as follows:
- For Swedish language:
[ { "registerID": 26001, "text": "Dubbelregistrerat" }, { "registerID": 26002, "text": "Felregistrerat" }, { "registerID": 26003, "text": "Testdata" } ] - For English language:
[ { "registerID": 26001, "text": "Double registered" }, { "registerID": 26002, "text": "Wrongly registered" }, { "registerID": 26003, "text": "Test data" } ]
As it can be seen, it is quite simple to expose custom reference data via Platina API without doing any code changes in API itself. You can create as many views for custom reference data as required.
Arbitrary custom data for object in JSON format
Platina API provides an easy way store and retrieve custom data in JSON format. It may be very useful when external cliens want to associate and access additional metadata with the Platina object without any additional development efforts on Platina API side. At the moment of creation of this article the following endpoints can be used to manage custom data of such kind:
- For documents:
/api/v7/documents/{id}/custom-data - For deeds:
/api/v7/deeds/{id}/custom-data - For journalized cases:
/api/v7/journalized-cases/{id}/custom-data
Each of mentioned above endpoints accepts HTTP GET or PUT requests to retrieve or store the custom data for object respectively.
Arbitrary custom data usage example
This section describes a basic scenario for associating and retrieving custom data with Platina object. For example purposes, let's assume the object's custom data is the following:
{
"customString": "Example string value",
"customInt": 1234,
"customFloat": 4.321,
"customDate": "2024-08-20T00:00:00",
"customArray": [1, 2, 3, 4, 5],
"nestedData": {
"nestedString": "Nested data string",
"nestedArray": ["a", "r", "r", "a", "y"]
}
}
Assuming that Platina already has a journalized case with ID that equals to MWz63LAym9Q, it is possible to send an HTTP PUT request with custom data object to /api/v7/journalized-cases/MWz63LAym9Q/custom-data endpoint. In case if custom data was successfully associated, the Platina API will respond with HTTP 200 OK response.
Now let's send an HTTP GET request to /api/v7/journalized-cases/MWz63LAym9Q/custom-data endpoint. Platina API shall respond with the same custom data JSON object that was sent with previous PUT request:
{
"customArray": [
1,
2,
3,
4,
5
],
"customDate": "2024-08-20T00:00:00",
"customFloat": 4.321,
"customInt": 1234,
"customString": "Example string value",
"nestedData": {
"nestedString": "Nested data string",
"nestedArray": [
"a",
"r",
"r",
"a",
"y"
]
}
}
Tech details about storage of custom JSON object in Platina database
When a valid request to associate a custom data in JSON format with Platina object is received by Platina API, the Platina API splits the data and preserves it in tCustomData table in platina_custom database. The tCustomData table has the following columns:
| Column | Description |
|---|---|
| propID | PropID of the object which to associate the custom data with. |
| customDataIdentifier | Identifier for the stored custom data value. |
| kind | Kind of custom data. By default equals to JSON, but could be also REF or REF_RO which will be described in details in the next chapter. |
| customData | Custom data value for JSON kind, or NULL for REF and REF_RO. |
As it was mentioned recently, Platina API splits received custom data object before storing it in the database. Such action is necessary for better handling of custom data that is associated with the object, which may include using SQL queries with JSON that require only portion of custom data, or support of non-trivial or existing customization scenarios which will be described further in this article. The data split is done for root custom data JSON object by its properties (all nested objects or arrays will be treated as JSON values). Below is an illustration of how the object's example custom data from parent chapter will be stored in tCustomData table:
| propID | customDataIdentifier | kind | customData |
|---|---|---|---|
| #PROPID# | customString | JSON | "Example string value" |
| #PROPID# | customInt | JSON | 1234 |
| #PROPID# | customFloat | JSON | 4.321 |
| #PROPID# | customDate | JSON | "2024-08-20T00:00:00" |
| #PROPID# | customArray | JSON | [1,2,3,4,5] |
| #PROPID# | nestedData | JSON | {nestedString":"Nested data string","nestedArray":["a","r","r","a","y"]} |
When Platina API receives a GET HTTP request to retrieve the custom data for an object, it queries associated data from tCustomData table by object's propID, then composes a JSON object from result and sends the composed JSON in response.
Support of existing and non-trivial customizations
This article already covered various options for handling custom data via Platina API, however there is still a gap in description of how the existing custom data or non-trivial customizations can be supported by Platina API. This chapter is inteded to close this gap and provide details and steps for implementing such support.
Let's consider the follwing example case: there is already an implemented customization that allows to specify for journalized case an additional information about organization that it currently belongs to. To not overcomplicate the example, custom data will include organization's Name andAddress that are stored in tExampleOrganization table of platina_custom database:
| propID | name | address |
|---|---|---|
| #PROPID# | Example Organization | Example str, 123 |
Make custom data available via Platina API in read only mode
Let's begin with the requirement to make custom information about organization available via Platina API in read only mode (read only mode in this context means that such data can be retrieved but cannot be modified via Platina API) and be provided as a value of ExampleOrganization property of returned custom data JSON object.
There are two possible ways to implement this requirement:
- By creating a custom view in
platina_customdatabase withCustomData_prefix (e.g.CustomData_ExampleOrganization) that selects data from custom table(s) and that haspropIDfield that stores object's ID with which the returned custom data is associated. - By implementing a custom stored procedure which name should start with
pCustomData_and end with_Get, e.g.pCustomData_ExampleOrganization_Get.
Important: Despite of chosen approach, the final step would be to register a reference to the set of custom data in
tCustomDatatable ofplatina_customdatabase (details about how to perform this are provided further in this article).
Note in case if both view and stored procedure were implemented for the same set of custom data, then Platina API will choose stored procedure as a source of custom data, which means that view will not be queried at all.
Create a CustomData_ExampleOrganization view
If follow the first option with custom view, then it is necessary to create a new view named CustomData_ExampleOrganization in platina_custom database:
USE platina_custom
GO
CREATE VIEW CustomData_ExampleOrganization
AS
SELECT [propID], [name], [address] FROM tExampleOrganization
GO
and register an ExampleOrganization custom data identifier in tCustomData table of platina_custom database as read only reference:
| propID | customDataIdentifier | kind | customData |
|---|---|---|---|
| #PROPID# | ExampleOrganization |
REF_RO | NULL |
Create a pCustomData_ExampleOrganization_Get procedure
In more non-trivial customization scenarios (user permission checks, multi-language support, etc) the option with custom stored procedure may be preferable. In addition to procedure naming requirements, the procedure must accept the following parameters that will be provided by Platina API when calling the procedure:
@propID INT- PropID of the object for which to retrieve the custom data.@usrPropID INT- PropID of the user that makes a request to Platina API.@langID INT- LangID for the user that makes a request to Platina API.
Considering this, let's create a stored procedure that returns an organization custom data for object:
CREATE PROCEDURE [dbo].[pCustomData_ExampleOrganization_Get]
@propID INT,
@usrPropID INT,
@langID INT
AS
BEGIN
SELECT [name], [address] FROM tExampleOrganization
WHERE [propID] = @propID
END
and register an ExampleOrganization custom data identifier in tCustomData table of platina_custom database as read only reference:
| propID | customDataIdentifier | kind | customData |
|---|---|---|---|
| #PROPID# | ExampleOrganization |
REF_RO | NULL |
Note the custom data identifier registration step is the same as in case with custom view.
Add support to change the custom data via Platina API
The previous chapter contains details of how to expose data from custom tables via Platina API. In many cases it is not enough, and usually it is also required to modify such data. This chapter contains a guide of how to make it possible.
Let's evolve an example from previous chapter and implement possibility to change the organization's data via Platina API an additionally storing the previous values in tExampleOrganizationHistory table of platina_custom database. Currently, there is only one option to achieve that - implement a custom stored procedure. Similarly to read only custom data procedure case, the stored procedure name should start with pCustomData_ but end with _Set, e.g. pCustomData_ExampleOrganization_Set. The stored procedure must accept the following default parameters:
@propID INT- PropID of the object for which to retrieve the custom data.@usrPropID INT- PropID of the user that makes a request to Platina API.Important Additionally, stored procedure must accept custom parameters that store custom data values from JSON object that will be sent in request body to Platina API. For better clarity, refer to example below where on the left there is a JSON object with custom data, and on the right - parameters of custom stored procedure that handles data from JSON object (such parameters should have
@pprefix and corresponding data type):JSON Stored Procedure Parameters { "customInt": 123, "customFloat": 4.56, "customString": "custom value", "customDateTime": "2024-08-20T00:00:00" }@propID INT, @usrPropID INT,
@pcustomInt INT, @pcustomFloat FLOAT, @pcustomString NVARCHAR(MAX), @pcustomDateTime DATETIME
Assuming that custom data update request to update organization data for object looking as follows:
{
"ExampleOrganization": {
"name": "Example Organization",
"address": "Example str, 123"
}
}
Then custom stored procedure must accept parameters named @pname and @paddress (@p-prefixed JSON object's property names) and may look like follows:
CREATE PROCEDURE [dbo].[pCustomData_ExampleOrganization_Set]
@propID INT,
@usrPropID INT,
@pname NVARCHAR(50),
@paddress NVARCHAR(50)
AS
BEGIN
IF NOT EXISTS (SELECT * FROM tExampleOrganization WHERE [propID] = @propID)
BEGIN
INSERT INTO tExampleOrganization ([propID], [name], [address])
VALUES (@propID, @pname, @paddress)
END
ELSE
BEGIN
INSERT INTO tExampleOrganizationHistory ([propID], [timestamp], [name], [address])
SELECT [propID], CURRENT_TIMESTAMP, [name], [address] FROM tExampleOrganization
WHERE [propID] = @propID
UPDATE tExampleOrganization SET
[name] = @pname, [address] = @paddress
WHERE [propID] = @propID
END
END
By exploring the stored procedure code it is easy to identify, that it covers non-trivial customization scenario - stores the history of changes in separate table. When procedure is created, it is necessary to update already existing ExampleOrganization registration (which was added in example for previous chapter) from REF_RO to REF kind. That will instruct Platina API that it can accept request to change the referenced custom data:
| propID | customDataIdentifier | kind | customData |
|---|---|---|---|
| #PROPID# | ExampleOrganization | REF |
NULL |
And finally let's also expose the history of changes in organization for object in ExampleOrganizationHistory property of custom data JSON object. To achieve that, let's create a custom procedure pCustomData_ExampleOrganizationHistory_Get:
CREATE PROCEDURE [dbo].[pCustomData_ExampleOrganizationHistory_Get]
@propID INT,
@usrPropID INT,
@langID INT
AS
BEGIN
SELECT [timestamp], [name], [address] FROM tExampleOrganizationHistory
WHERE [propID] = @propID
ORDER BY [timestamp] ASC
END
Since history data is not intended to be modifieable, we can register it as read only reference in tCustomData table:
| propID | customDataIdentifier | kind | customData |
|---|---|---|---|
| #PROPID# | ExampleOrganization | REF |
NULL |
#PROPID# |
ExampleOrganizationHistory |
REF_RO |
NULL |
Registering custom data reference in tCustomData table
As mentioned earlier in this article, tCustomData table has a kind field that could be JSON, REF or REF_RO. To register a custom data identifier as reference to view or stored procedure as data source, the REF or REF_RO kind is used:
REFfor registering a data source that supports both read and edit data.REF_ROfor registering a data source that only provides data that can be read.
Useful tips
- Since
REFandREF_ROcustom identifier registrations shall exist for any object that could have associated custom data from custom tables, Platina sequences can be useful to automatically add such registrations on object creation.