Logo

    Home

    Documentation

    Use Cases

    Training

    Applications

    Release Notes

    Multi-Tenancy outside of the Progress OpenEdge Standard

    Multi-Tenancy outside of the Progress OpenEdge Standard

    • Description
    • Basic information
    • How to use it

    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 the Akioma.Swat.OERA.Context.ISessionHandler service from services.xml. It will be used from the SwatSessionContextWebHandler webHandler on GET/PUT operations.
        • 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.
        • CustomSessionManager: A custom SessionManager class is needed, which inherits the Akioma.Swat.SessionManager class. Here, we need to override the InitializeSessionFromUser method. This is where you will fill in the custom properties from specified in the ContextWrapper.
          1. 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.
          2. The custom SessionManager class needs to be specified in a new custom SessionActivator class. The new activator class needs to override the Consultingwerk.Framework.Server.RestServerSessionActivator,Akioma.Swat.System.SwatServerSessionActivator from services.xml so it will be called whenever a new session is created.
          3. 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.
    • 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 the ServerEventsHandler 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.SwatServiceCustomizationclass and override the EnhanceFetchDataRequest method. Here you can inject custom parameters into the WHERE clause of the query. Then, you need to override the Consultingwerk.OERA.JsdoGenericService.IGenericServiceCustomization service from services.xml with the new 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:

        https://consultingwerk.atlassian.net/wiki/spaces/SCL/pages/8094346/REST+Service+Authentication+using+the+SmartFramework+IHybridRealm+implementation

        Sample services.xml

    Back to DocumentationDocumentation

    Back to Home Page

    Logo
    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.
    <?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>