Question
· Oct 16, 2023

Surcharge les méthodes SendRequestAsync et SendRequestSync

Bonjour,

Je travaille sur l'ajout d'étapes préliminaires avant l'envoi d'un message d'un Business Service à un Business Process.

Pour ce faire, je surcharge les méthodes SendRequestAsync et SendRequestSync, mettant ces étapes préliminaires au début et quittant avec ##super return.

Je veux garder le comportement natif de SendRequestAsync et SendRequestSync. Je veux aussi coller à l'utilisation de ##super, donc si ces méthodes changent dans une mise à jour annuelle d'Iris, je n'aurai rien à changer. À cet égard, la classe personnalisée que j'ai faite doit étendre Ens.BusinessService. Voici à quoi elle ressemble :

Class XXXX.OUTILS.BS.AuditCommons Extends Ens.BusinessService

{



/// Class of the audit to use for this flow. If the flow use

/// iterative process, for instance a loop to inserted multiple

/// lines in a table, you can use its insertdetails() method to

/// log an error that occured in the loop without poluting

/// the journal.

Property auditClass As %String(MAXLEN = 500) [ InitialExpression = "XXXX.REPORTING.Tables.Commons.Main" ];



/// Name of the flow that will be inserted in the audit.

Property flowName As %String(MAXLEN = 500);



/// Source of the flow that will be inserted in the audit.

Property flowSource As %String(MAXLEN = 500);



/// Target of the flow that will be inserted in the audit.

Property flowTarget As %String(MAXLEN = 500);



/// If true, the message will be sent synchronously.

Property asynchronous As %Boolean;



/// Audit instance of the current session

Property audit As XXXX.REPORTING.Tables.Commons.Main;



Property errMsg As %String(MAXLEN = "");



Parameter auditLogError = 1;



Parameter SETTINGS = "auditClass:flowParameters,flowName:flowParameters,flowSource:flowParameters,flowTarget:flowParameters,asynchronous:flowParameters";



Method SendRequestSync(pTargetDispatchName As %String, pRequest As %Library.Persistent, ByRef pResponse As %Library.Persistent, pTimeout As %Numeric = -1, pDescription As %String = "") As %Status

{

    if ..asynchronous quit ..SendRequestAsync(pTargetDispatchName, pRequest, pDescription)

    set pResponse = ""

    set sc = ..initAudit(pRequest) quit:'sc sc

    set req = ..setReq(pRequest, .sc) quit:'sc sc

    quit ##super(pTargetDispatchName, req, .pResponse, .pTimeout, pDescription)

}



Method SendRequestAsync(pTargetDispatchName As %String, pRequest As %Library.Persistent, pDescription As %String = "") As %Status

{

    if '..asynchronous quit ..SendRequestSync(pTargetDispatchName, pRequest, .pResponse, .pTimout, pDescription)

    set sc = ..initAudit(pRequest) quit:'sc sc

    set req = ..setReq(pRequest, .sc) quit:'sc sc

    quit ##super(pTargetDispatchName, req, pDescription)

}



/// Initialize an audit for the current session. You can

/// use the audit instance using the ..audit property.

Method initAudit(req As Ens.Request = "") As %Status

{

    do ..generateSession()



    set auditData = {

        "session": (..%SessionId)

        ,"flowName": (..flowName)

        ,"flowSource": (..flowSource)

        ,"flowTarget": (..flowTarget)

        ,"filename": (##class(XXXX.OUTILS.File.FileInfos).getFilename(req))

    }



    set ..audit = $CLASSMETHOD(..auditClass, "initialize", auditData, .sc)

    if 'sc return ..error("<Initialize audit>",, "initAudit", sc).AsStatus()



    if ..errMsg '= "" do ..audit.setError(..errMsg, ..#auditLogError)

    return 1

}



Method setReq(pRequest As %Library.Persistent, Output gsc As %String = 1) As Ens.Request

{

    return pRequest

}



Method generateSession()

{

    set (..%SessionId,$$$JobSessionId) = ##class(Ens.MessageHeader).%New().MessageId()

}



ClassMethod OnGetConnections(pArray As %String, item As Ens.Config.Item) As %Status

{

    do ##class(XXXX.OUTILS.Admin).linkBsToBp(.pArray, .item)

}



/// Method used as an alias to shorten the code.

ClassMethod error(name As %String, code As %String = "", location As %String = "", data As %String = "") As %Exception.General

{

    return ##class(%Exception.General).%New(name, code, location, data)

}



}

J'utilise cette classe pour créer plusieurs classes de service que j'utiliserai à la place de la classe native. Par exemple, au lieu d'utiliser EnsLib.RecordMap.Service.ComplexBatchFTPService, j'utiliserai celle-ci :

Class XXXX.OUTILS.BS.ComplexMap.FTP Extends (EnsLib.RecordMap.Service.ComplexBatchFTPService, XXXX.OUTILS.BS.AuditCommons)

{

Method SendRequestSync(pTargetDispatchName As %String, pRequest As %Library.Persistent, ByRef pResponse As %Library.Persistent, pTimeout As %Numeric = -1, pDescription As %String = "") As %Status

{

    if ..asynchronous quit ..SendRequestAsync(pTargetDispatchName, pRequest, pDescription)

    set pResponse = ""

    set sc = ..initAudit(pRequest) quit:'sc sc

    set req = ..setReq(pRequest, .sc) quit:'sc sc

    quit ##super(pTargetDispatchName, req, .pResponse, .pTimeout, pDescription)

}



Method SendRequestAsync(pTargetDispatchName As %String, pRequest As %Library.Persistent, pDescription As %String = "") As %Status

{

    if '..asynchronous quit ..SendRequestSync(pTargetDispatchName, pRequest, .pResponse, .pTimout, pDescription)

    set sc = ..initAudit(pRequest) quit:'sc sc

    set req = ..setReq(pRequest, .sc) quit:'sc sc

    quit ##super(pTargetDispatchName, req, pDescription)

}



}

Comme vous pouvez le voir, les méthodes SendRequestAsync et SendRequestSync sont également dans cette classe. C'est parce que je ne peux pas mettre ma classe personnalisée comme premier élément dans la liste des extends. Si je le fais, les méthodes et les valeurs des paramètres de Ens.BusinessService dans ma méthode personnalisée prendraient le pas sur celles de EnsLib.RecordMap.Service.ComplexBatchFTPService.

Ça fonctionne bien comme ça, mais ça me dérange aussi que ces méthodes doivent être dupliquées dans chacune de ces classes de service personnalisées. Alors voici ma question : Y a-t-il un moyen de définir les méthodes surchargées de ma classe personnalisée pour qu'elles aient la priorité ? Je voudrais que ma classe ComplexeMap et toutes les autres ressemblent à ceci :

Class XXXX.OUTILS.BS.ComplexMap.FTP Extends (EnsLib.RecordMap.Service.ComplexBatchFTPService, XXXX.OUTILS.BS.AuditCommons)

{



}

Nous utilisons IRIS for Windows (x86-64) 2022.1 (Build 209U)

Merci d'avance, et bonne journée.

Discussion (5)2
Connectez-vous ou inscrivez-vous pour continuer

Bonjour Jules,

Question très intéressante.

Sinon, une autre idée peut etre que plutot utiliser la surcharge de méthode pour l'audit, tu pourrais utiliser une macro comme ceci (l'idée me vient d'HealthShare):

ROUTINE XXXX.TraceHelper [Type=INC]
#def1Arg XXXXTRACE(%args) DO:..SendRequestAsync(%args)

Apres tu peux utiliser la macro XXXXTRACE dans tes méthodes pour faire l'audit.

exemple :

Include XXXX.TraceHelper
Class XXXX.OUTILS.BS.ComplexMap.FTP Extends (EnsLib.RecordMap.Service.ComplexBatchFTPService)
{

Parameter ADAPTER = "EnsLib.RecordMap.Service.FileServiceAdapter";

Method OnProcessInput(pInput As %RegisteredObject, Output pOutput As %RegisteredObject) As %Status
{
    $$$XXXXTRACE("MyAudditTarget","MyauditMessage",0)
    Quit $$$OK

}

}

Cordialement,

Bonsoir,

Comme l'a expliqué @Guillaume Rongier , modifier l'ordre de l'héritage peut apporter une solution, mais dans votre cas je me demande si ça ne va pas apporter d'autres problèmes.

Si c'est le cas, il faudrait que la classe "Common" n'hérite de rien du tout.

Il faut ensuite créer les méthodes avec les keywords [codemode = objectgenerator et ForceGenerate].

Cela permettra d'injecter le code dans les sous classe de AuditCommons

Par exemple, la méthode SendRequestSync pourrait ressembler à cela : 

Class XXXX.Outils.BS.AuditCommons
{
..
Method SendRequestSync(pTargetDispatchName As %String, pRequest As %Library.Persistent, ByRef pResponse As %Library.Persistent, pTimeout As %Numeric = -1, pDescription As %String = "") As %Status [ CodeMode = objectgenerator, ForceGenerate ]
{
	; Le code ne peut être généré que dans les sous classes
	If %class.Name = "XXXX.Outils.BS.AuditCommons" Quit $$$OK
	Do %code.WriteLine($Char(9) _ "if ..asynchronous quit ..SendRequestAsync(pTargetDispatchName, pRequest, pDescription)")
	Do %code.WriteLine($Char(9) _ "set pResponse = """"")
	Do %code.WriteLine($Char(9) _ "set sc = ..initAudit(pRequest) quit:'sc sc")
	Do %code.WriteLine($Char(9) _ "set req = ..setReq(pRequest, .sc) quit:'sc sc")
	Do %code.WriteLine($Char(9) _ "quit ##super(pTargetDispatchName, req, .pResponse, .pTimeout, pDescription)")
	
	Quit $$$OK
}

Je vais tenter de vous fournir un exemple plus complet sur base de la classe common que vous avez envoyé.

Voilà, j'ai tenté d'adapter votre classe common avec du codemode = objectgenerator:

Include Ensemble

Class XXXX.Outils.BS.AuditCommons
{

/// Class of the audit to use for this flow. If the flow use
/// iterative process, for instance a loop to inserted multiple
/// lines in a table, you can use its insertdetails() method to
/// log an error that occured in the loop without poluting
/// the journal.
Property auditClass As %String(MAXLEN = 500) [ InitialExpression = "XXXX.REPORTING.Tables.Commons.Main" ];
/// Name of the flow that will be inserted in the audit.
Property flowName As %String(MAXLEN = 500);
/// Source of the flow that will be inserted in the audit.
Property flowSource As %String(MAXLEN = 500);
/// Target of the flow that will be inserted in the audit.
Property flowTarget As %String(MAXLEN = 500);
/// If true, the message will be sent synchronously.
Property asynchronous As %Boolean;
/// Audit instance of the current session /// XXXX.REPORTING.Tables.Commons.Main;
Property audit As XXXX.REPORTING.Tables.Commons.Main;
Property errMsg As %String(MAXLEN = "");
Parameter auditLogError = 1;
Parameter SETTINGS = "auditClass:flowParameters,flowName:flowParameters,flowSource:flowParameters,flowTarget:flowParameters,asynchronous:flowParameters";
Method SendRequestSync(pTargetDispatchName As %String, pRequest As %Library.Persistent, ByRef pResponse As %Library.Persistent, pTimeout As %Numeric = -1, pDescription As %String = "") As %Status [ CodeMode = objectgenerator, ForceGenerate ]
{
	; Le code ne peut être généré que dans les sous classes
	If %class.Name = "XXXX.Outils.BS.AuditCommons" Quit $$$OK
	Do %code.WriteLine($Char(9) _ "if ..asynchronous quit ..SendRequestAsync(pTargetDispatchName, pRequest, pDescription)")
	Do %code.WriteLine($Char(9) _ "set pResponse = """"")
	Do %code.WriteLine($Char(9) _ "set sc = ..initAudit(pRequest) quit:'sc sc")
	Do %code.WriteLine($Char(9) _ "set req = ..setReq(pRequest, .sc) quit:'sc sc")
	Do %code.WriteLine($Char(9) _ "quit ##super(pTargetDispatchName, req, .pResponse, .pTimeout, pDescription)")
	
	Quit $$$OK
}

Method SendRequestAsync(pTargetDispatchName As %String, pRequest As %Library.Persistent, pDescription As %String = "") As %Status [ CodeMode = objectgenerator, ForceGenerate ]
{
	; Le code ne peut être généré que dans les sous classes
	If %class.Name = "XXXX.Outils.BS.AuditCommons" Quit $$$OK
	Do %code.WriteLine($Char(9) _ "if '..asynchronous quit ..SendRequestSync(pTargetDispatchName, pRequest, .pResponse, .pTimout, pDescription)")
	Do %code.WriteLine($Char(9) _ "set sc = ..initAudit(pRequest) quit:'sc sc")
	Do %code.WriteLine($Char(9) _ "set req = ..setReq(pRequest, .sc) quit:'sc sc")
	Do %code.WriteLine($Char(9) _ "quit ##super(pTargetDispatchName, req, pDescription)")
}

/// Initialize an audit for the current session. You can
/// use the audit instance using the ..audit property.
Method initAudit(req As Ens.Request = "") As %Status
{
    do ..generateSession()
 
    set auditData = {
        "session": (..getSessionId())
        ,"flowName": (..flowName)
        ,"flowSource": (..flowSource)
        ,"flowTarget": (..flowTarget)
        ,"filename": (##class(XXXX.OUTILS.File.FileInfos).getFilename(req))
    }

    set ..audit = $CLASSMETHOD(..auditClass, "initialize", auditData, .sc)

    if 'sc return ..error("<Initialize audit>",, "initAudit", sc).AsStatus()

    if ..errMsg '= "" do ..audit.setError(..errMsg, ..#auditLogError)

    return 1
}

Method setReq(pRequest As %Library.Persistent, Output gsc As %String = 1) As Ens.Request
{
    return pRequest
}

Method generateSession() [ CodeMode = objectgenerator, ForceGenerate ]
{
	; Le code ne peut être généré que dans les sous classes
	If %class.Name = "XXXX.Outils.BS.AuditCommons" Quit $$$OK
    Do %code.WriteLine($Char(9)_"Set (..%SessionId,$$$JobSessionId) = ##class(Ens.MessageHeader).%New().MessageId()")
}

Method getSessionId() As %String [ CodeMode = objectgenerator, ForceGenerate ]
{
	; Le code ne peut être généré que dans les sous classes
	If %class.Name = "XXXX.Outils.BS.AuditCommons" Quit $$$OK
    Do %code.WriteLine($Char(9) _ "Quit ..%SessionId")
}

ClassMethod OnGetConnections(pArray As %String, item As Ens.Config.Item) As %Status
{
    do ##class(XXXX.OUTILS.Admin).linkBsToBp(.pArray, .item)
}

/// Method used as an alias to shorten the code.
ClassMethod error(name As %String, code As %String = "", location As %String = "", data As %String = "") As %Exception.General
{
    return ##class(%Exception.General).%New(name, code, location, data)
}

}

ensuite, pour la classe "XXXX.Outils.BS.ComplexMap.FTP", modifiez juste l'ordre de l'héritage : 

Class XXXX.Outils.BS.ComplexMap.FTP Extends (XXXX.Outils.BS.AuditCommons, EnsLib.RecordMap.Service.ComplexBatchFTPService)
{

}

Après compilation si vous regardez le code de la routine générée pour la classe ComplexMap.FTP (ctrl+maj+v) vous pourrez remarquer que le code des méthodes [ codemode = objectgenerator, ForceGenerate] a bien été inclus:

zSendRequestAsync(pTargetDispatchName,pRequest,pDescription="") public {
	if '..asynchronous quit ..SendRequestSync(pTargetDispatchName, pRequest, .pResponse, .pTimout, pDescription)
	set sc = ..initAudit(pRequest) quit:'sc sc
	set req = ..setReq(pRequest, .sc) quit:'sc sc
	quit ##class(XXXX.Outils.BS.AuditCommons)$this.SendRequestAsync(pTargetDispatchName, req, pDescription)
}
zSendRequestSync(pTargetDispatchName,pRequest,pResponse,pTimeout=-1,pDescription="") public {
	if ..asynchronous quit ..SendRequestAsync(pTargetDispatchName, pRequest, pDescription)
	set pResponse = ""
	set sc = ..initAudit(pRequest) quit:'sc sc
	set req = ..setReq(pRequest, .sc) quit:'sc sc
	quit ##class(XXXX.Outils.BS.AuditCommons)$this.SendRequestSync(pTargetDispatchName, req, .pResponse, .pTimeout, pDescription)
}
zgenerateSession() public {
	Set (..%SessionId,%Ensemble("SessionId")) = ##class(Ens.MessageHeader).%New().MessageId()
}
zgetSessionId() public {
	Quit ..%SessionId }

Logiquement comme la classe common n'hérite plus de Ens.BusinessService, le fait qu'elle soit la classe la plus à gauche dans l'ordre d'héritage ne devrait plus avoir d'influence négative.

Bonjour,

Merci beaucoup pour ces solutions ! Celle des méthodes générées semble être prometteuse puisque nous avons conçu ce système pour pouvoir hériter des classes à chaque fois qu'un élément plus spécifique doit être donné.

La méthode setReq() permet par exemple sur certain de nos flux de mettre à jour le message avant l'envoi et est donc surchargé avec toute une procédure.

J'ai essayé d'utiliser la génération, mais je suis confronté à un problème : les méthodes semblent se réécrire sur chaque classe filles. L'audit insérant une ligne dans une table, on voit bien les 3 lignes quand l'héritage est fait sur 3 niveaux. Y a-t-il un moyen de contrôler ça ?

Bonsoir @Jules Pontois 

Oui dans le cas ou une classe hérite de "XXXX.Outils.BS.ComplexMap.FTP" effectivement les méthodes sont générées dans toutes les classes et les appels ##super ne vous arrange pas.

En fait, si on retire le keyword "ForceGenerate" des méthodes "objectgenerator", les méthodes ne seront générées que dans la sous classe direct.  J'ai utilisé ce keyword pour être sûr que ce soit au moins généré dans sa première sous-classe, mais après avoir fait un test sur ma machine, ce n'est visiblement pas nécessaire.  Vous pouvez donc retirer ce keyword de la classe common, cela devrait régler le problème.

Toutefois si dans l'une de vos sous classes vous faites un override de SendRequestSync\SendRequestAsync en se terminant par ##super, la méthode exécutée par le ##super sera celle injectée par la sous classe direct de "AuditCommons".  Ce n'est pas vraiment un problème, car nous pouvons contrôler cela via un "Parameter" ou encore plus simplement avec un Cast, exemple:

Class XXXX.Outils.BS.ComplexMap.Sub Extends XXXX.Outils.BS.ComplexMap.FTP
{

Method SendRequestSync(pTargetDispatchName As %String, pRequest As %Library.Persistent, ByRef pResponse As %Library.Persistent, pTimeout As %Numeric = -1, pDescription As %String = "") As %Status
{
    ; ...
    ; lignes de code spécifiques à cette classe
    ; bla bla
    ; ...
    ; maintenant on délègue à EnsLib.RecordMap.Service.ComplexBatchFTPService sans passer
    ; par la méthode générée dans XXXX.Outils.BS.ComplexMap.FTP
    Quit ##class(EnsLib.RecordMap.Service.ComplexBatchFTPService)##this.SendRequestSync(pTargetDispatchName, pRequest, .pResponse, pTimeout, pDescription)
}

}