Description
This How-To is for customers, which replace some parts of their existing application and have a Multi-Tenant logic implemented by having the tenantId in every table.
Here we would like to give a high-level overview how to adopt this logic with Build.One
Basic information
- Store the tenantId in the user session
- Read the tenantId of the current user in each read or write query
How to use it
- Store the tenantId in the user session
- Build.One offers the possibility to manipulate and create own session variables
- The detailed use is described here: https://help.build.one/display/AKDOC/Implementing+custom+session+properties
- Create a new context dataset including temp table with the new attribute
- SessionHandler: A new SessionHandler class is required. This needs to inherit from
Akioma.Swat.OERA.Context.SwatSessionHandler
and override the class constructor. This new class needs to override theAkioma.Swat.OERA.Context.ISessionHandler
service from services.xml. It will be used from the SwatSessionContextWebHandler webHandler on GET/PUT operations. - CustomSessionManager: A custom SessionManager class is needed, which inherits the
Akioma.Swat.SessionManager
class. Here, we need to override theInitializeSessionFromUser
method. This is where you will fill in the custom properties from specified in theContextWrapper
. - The custom SessionManager class needs to be specified in a new custom
SessionActivator
class. The new activator class needs to override theConsultingwerk.Framework.Server.RestServerSessionActivator,Akioma.Swat.System.SwatServerSessionActivator
from services.xml so it will be called whenever a new session is created. - Adjust the queries in a central place
- For all write queries this can be done in the *EventHandler.cls.
- In order to achieve this, you need to implement a custom event handler class that inherits from the
Akioma.Swat.OERA.Dynamic.BaseServerEventHandler
and override the onBeforeSave Method. After this is done, in theServerEventsHandler
attribute of the DSO you need to manually specify the newly created class (full class name). - For all read queries this can be done in the *ServiceCustomization.cls. You need to provide a custom implementation that inherits the
Akioma.Swat.OERA.SwatServiceCustomization
class and override theEnhanceFetchDataRequest
method. Here you can inject custom parameters into the WHERE clause of the query. Then, you need to override theConsultingwerk.OERA.JsdoGenericService.IGenericServiceCustomization
service from services.xml with the new class.
CLASS My.Custom.SessionHandler
INHERITS Akioma.Swat.OERA.Context.SwatSessionHandler:
CONSTRUCTOR SessionHandler():
SUPER().
THIS-OBJECT:RegisterSession("eCustomSessionContext",
"My.Custom.ContextWrapper",
"",
"my-custom-field",
?).
END CONSTRUCTOR.
END CLASS.
CLASS My.Custom.SessionManager INHERITS Akioma.Swat.SessionManager:
METHOD PUBLIC STATIC OVERRIDE VOID InitializeSessionFromUser(pcUser AS CHARACTER, pcDomain AS CHARACTER):
...
Akioma.Swat.SessionManager:InitializeSessionFromUser(pcUser , pcDomain).
//assign custom properties based on the used here
END METHOD.
END CLASS.
CLASS My.Custom.SessionActivator INHERITS Akioma.Swat.System.SwatServerSessionActivator:
METHOD PROTECTED OVERRIDE VOID InitializeSessionFromUser(pcUser AS CHARACTER, pcDomain AS CHARACTER):
My.Custom.SessionManager:InitializeSessionFromUser(pcUser, pcDomain).
END METHOD.
END CLASS.
CLASS My.Custom.EventHandler
INHERITS Akioma.Swat.OERA.Dynamic.BaseServerEventHandler:
//we use ttMock in this case to make the EventHandler generic
DEFINE TEMP-TABLE ttMock NO-UNDO
FIELD MockField AS CHARACTER
.
DEFINE DATASET dsMock FOR ttMock.
CONSTRUCTOR EventHandler():
THIS-OBJECT(DATASET dsMock:HANDLE).
END CONSTRUCTOR.
CONSTRUCTOR EventHandler(phDataset AS HANDLE):
SUPER(phDataset).
END CONSTRUCTOR.
METHOD PUBLIC OVERRIDE VOID OnBeforeSave(INPUT-OUTPUT DATASET-HANDLE phDataset):
DEFINE VARIABLE hBuffer AS HANDLE NO-UNDO.
DEFINE VARIABLE hQuery AS HANDLE NO-UNDO.
hBuffer = phDataset:GET-TOP-BUFFER.
IF VALID-HANDLE(hBuffer:BUFFER-FIELD("my-custom-field")) THEN
DO:
hQuery = Consultingwerk.Util.QueryHelper:CreatePreparedQuery(hBuffer).
Consultingwerk.Util.DatasetHelper:SetTrackingChanges(phDataset, TRUE).
DO WHILE NOT hQuery:QUERY-OFF-END
ON ERROR UNDO, NEXT:
CASE hBuffer:ROW-STATE:
WHEN ROW-CREATED THEN
ASSIGN hBuffer::my-custom-field = "new-value".
END CASE.
FINALLY:
hQuery:GET-NEXT().
END FINALLY.
END.
Consultingwerk.Util.DatasetHelper:SetTrackingChanges(phDataset, FALSE).
END.
SUPER:OnBeforeSave(DATASET-HANDLE phDataset).
FINALLY:
Consultingwerk.Util.GarbageCollectorHelper:DeleteObject(hQuery).
END FINALLY.
END METHOD.
END CLASS.
CLASS My.Custom.ServiceCustomization INHERITS SwatServiceCustomization:
METHOD PROTECTED OVERRIDE VOID EnhanceFetchDataRequest( INPUT pcEntityName AS CHARACTER, INPUT poWebRequest AS OpenEdge.Web.IWebRequest, INPUT poFetchDataRequest AS Consultingwerk.OERA.IFetchDataRequest ):
//get custom field value
...
DO iIndex = 1 TO NUM-ENTRIES(poFetchDataRequest:Tables, ","):
hBuffer = hDataset:GET-BUFFER-HANDLE(ENTRY(iIndex, poFetchDataRequest:Tables, ",")).
cQuery = Akioma.Swat.Util.QueryParserHelper:GetQueryForTable(hBuffer, hBuffer:NAME EQ cMainTableName,
pcEntityName, oAkiomaQueryJson, poFetchDataRequest).
IF cCustomValue NE "*" AND Consultingwerk.Util.BufferHelper:HasField(hBuffer, "my-custom-field") THEN
DO:
cCustomClause = SUBSTITUTE("&1.my-custom-field = &2", hBuffer:NAME, cCustomValue).
IF INDEX(cQuery, "WHERE") > 0 THEN
cQuery = SUBSTITUTE("&1 &2", cQuery, SUBSTITUTE(" AND (&1)", cCustomClause)).
ELSE
cQuery = SUBSTITUTE("&1 &2", cQuery, SUBSTITUTE(" WHERE (&1)", cCustomClause)).
END.
cQueries = SUBSTITUTE("&1&2&3", cQueries, CHR(1), cQuery).
END.
cQueries = TRIM(cQueries, CHR(1)).
poFetchDataRequest:Queries = cQueries.
...
END METHOD.
END CLASS.
- All services will be set in the service.xml file
- The following services need to be overridden in the xml:
Consultingwerk.OERA.Context.IContextDatasetFactory
Akioma.Swat.OERA.Context.ISessionHandler
Consultingwerk.OERA.JsdoGenericService.IGenericServiceCustomization
Consultingwerk.Framework.Server.RestServerSessionActivator,Akioma.Swat.System.SwatServerSessionActivator
More info about the service authentication:
Sample services.xml
<?xml version="1.0"?>
<ttServiceLoader xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ttServiceLoaderRow>
<Order>211</Order>
<ServiceTypeName>Consultingwerk.OERA.Context.IContextDatasetFactory</ServiceTypeName>
<ServiceClassName>My.Context.ContextDatasetFactory</ServiceClassName>
</ttServiceLoaderRow>
<ttServiceLoaderRow>
<Order>212</Order>
<ServiceTypeName>Akioma.Swat.OERA.Context.ISessionHandler</ServiceTypeName>
<ServiceClassName>My.Custom.SessionHandler</ServiceClassName>
</ttServiceLoaderRow>
<ttServiceLoaderRow>
<Order>213</Order>
<ServiceTypeName>Consultingwerk.OERA.JsdoGenericService.IGenericServiceCustomization</ServiceTypeName>
<ServiceClassName>My.Custom.ServiceCustomization</ServiceClassName>
</ttServiceLoaderRow>
<ttServiceLoaderRow>
<Order>30</Order>
<ServiceTypeName>Consultingwerk.Framework.Server.RestServerSessionActivator,Akioma.Swat.System.SwatServerSessionActivator</ServiceTypeName>
<ServiceClassName>My.Custom.SessionActivator</ServiceClassName>
</ttServiceLoaderRow>
</ttServiceLoader>
Back to Documentation
Back to Home Page