Cool, let me know if it works. I have a similar situation for a BPL I am intending to write and I know I will have the same issue you are trying to resolve so I will be keen to know if this approach works because it is something I have been thinking about in anticipation of writing my BPL. My BPL will have a call to a DTL and I want to pass my context object to the Business Rule and then pass it into the DTL as the 'aux' object which is the 3rd parameter in the Transform() method as in ##class(MyDTL).Transform(pRequest,.pResponse,aux)

This works fine in custom code in a Business Process as aux can be any instance of a class but if the DTL is invoked from a Business Rule the aux is a specific object instance that holds properties that tell you about the rule that invoked the DTL  and I'm not sure I can pass my context object into the Transform as AUX if the DTL is instantiating it's own AUX object

I guess if I seemlesly pass the context object into the DTL via a Business Rule then I'll just have to invoke the DTL as a line of custom code in the BPL rather than as a Business Rule.

Nigel

Hi

Can you upload the class definition of the table that contains the data as well as the routines that were generated by the sql compiler i.e. the classes and routines for *.SMTKTUAT.*.* (.cls, *.int)

Have you tried accessing the data through the Management Portal->Stystem Explorer->SQL->Execute Query?

Have you tried setting up an ODBC DSN and then accessing the table from within say Excel?

If you set up a Windows ODBC DSN you can turn on logging for the DSN

To resolve this issue you need to isolate whether the issue lies within the code generated by IRIS for the sql statement, or whether it is the ODBC/JDBC connection functionality.

Have you written a simple class method using %Library.ResultSet as in

classmethod TestQuery() as %Status

{

      set tSC=$$$OK

      try {

              set rs=##class(%ResultSet)).%New("%DynamicQuery:SQL")

              set tSC=rs.Prepare("select col1, col2, ...., colN from Table where ....") if 'tSC quit

             set tSC=rs.Execute() if 'tSC quit

             while rs.Next(.tSC) {

                     if 'tSC quit

                     write !,rs.Data("Col1")," ",rs.Data("Col2")," ", ...., rs.Data("ColN")

           }

         }

         catch ex {set tSC=ex.AsStatus()}

        if 'tSC write !,$system.Status.GetErrorText(tsc)

        quit tSC

}

Nigel

The first thought that came to me on this would be to define a class named MyContextClass which contains all of the many properties, collections, serial objects and so on and then define the Context Object to have one Property, 'Properties' whose data type is MyContextClass.

Then in the Business Rule you can use the syntax context.Properties.PropA and so on

Nigel

Hi Goran

I am going to attach a set of classes from a real life application. I am going to put the class definitions into a word document. I would send you the XML but if you import the XML the classes won'r compile as there are too many claseses referenced in my code that I can't include.

The Basic overview of this production is that I have a Business Service that is processing a queue of messages that are created when data is trickle fed into a Data Warehouse. In my Business Process my request object contains a single property that is the MessageID of the message processed by the Business Service. 

In the Business service I open up the Message and from that I extract properties such as the Patient ID, three key fields LogType (where the data came from: PMI, In-Patient, Out-Patient...), a TransactionType that tells me more about what table in the Data Store is being Updated and an Action which is Add/Revise/Delete.

The Business Process  opens a configuration record from which it gets certain fields that give this Production Context.  I then find the correct Patient Object and I use a DTL to assign fileds in my Patient Class and other linked classes and populate a Standard HL7 ADT Message. The configuration settings include fields like Sending Facility, Sending Application, Receiving Application, Receiving Facility. These need to be sent into the DTL to populate these fields in the HL7 MSH header record. So I have created a custom AUX class which I can populate these fields within the DTL.

I then do another check to see if the data I am processing belongs to In-Patients or Out-Patients in which case I want to populate the Facility Code in the Visit Segment with the Facility code which I have transformed from an Application specific value into a National Facility Code.

Finally, if the data being processed is a Patient Merge then the ADT message structure has a different structure so I invoke another DTL to convert the ADT_A01 message into an ADT_A39 structure.

Finally I send the resultant message to an HTTP Operation and optionally to a File Operation.

I also have a build in Debugging class where I can write selected Debug information. In Development and UAT I want debugging turned on but in Production I don't so I have a property in the configuration that is a boolean flag that determines whether the debug message should be created or not.

Finally the configuration record also tells me how long I want to retain Debug Messages, Message Queue Messages, Ensemble Messages and Ensemble  Trace Logs and I have a business service that runs once a day and clears down any data that is older than the number of days I want to retain this data.

I have included the class definitions for all of the major classes I have described above.

Yours

Nigel 

Hi

Bear in mind that when you are working with DTL's when you invoke the Transform() method there is a third parameter called aux. The aux object can be an instance of any class you design and can contain any properties you desire. These are typically values that you want to use in the DTL that are not part of your request object.

When you call the DTL you create a new instance of your aux class and then when you invoke the DTL Transform you can write the following code:

set tSC=##class({Your DTL}).Transform(request,.response, .aux) where aux is an instance of your custom aux class.

This is true also of DTL's called from a BPL with one exception. If the DTL is invoked from a Business Rule then the Business rule creates an instance of a system defined aux object that contains information about which rule invoked the DTL and so on. 

I am busy trying to convert a Custom Code Business Process that will invoke a DTL and am still trying to get my head around what variables/objects are available in the BPL (Context) and the DTL.

If you resolve how to create a BPL using the context object and then invoke a DTL into which you can pass data other than the request and response message I would be interested to know how if you have success and how you managed to get it to work.

Yours

Nigel

Hi

Here is an example of a HTTP Operation POSTing HL7 messages. this HTTP request uses HTTPS and a Proxy Server. The lines I have highlighted in Yellow will be different in your case as you are sending JSON. Not clear if you are using FHIR. However your ContentType will most likely be application/json or application/json+fhir

    set tSC=$$$OK,pResponse=""
    try {
        set message=pRequest.OutputToString(,,.tSC) if 'tSC quit
        set tResponse = ##class(%Net.HttpResponse).%New(),tResponse.ContentType="application/hl7-v2"
        if '$IsObject(..HTTPRequest) {
            set pHttpRequest=##class(%Net.HttpRequest).%New()
            set pHttpRequest.Server=..Adapter.HTTPServer
            set pHttpRequest.Port=..Adapter.HTTPPort
            set pHttpRequest.ProxyServer=..Adapter.ProxyServer
            set pHttpRequest.ProxyPort=..Adapter.ProxyPort
            set pHttpRequest.ProxyTunnel=..Adapter.ProxyHttpTunnel
            set pHttpRequest.ProxyHTTPS=1
            set pHttpRequest.SSLConfiguration=..Adapter.SSLConfig
            set pHttpRequest.UserAgent="curl/7.29.0"
            set pHttpRequest.ContentEncoding="HL7-ER7"
            set pHttpRequest.ContentCharset="UTF-8"
            set pHttpRequest.ContentType="application/hl7-v2"
            set pHttpRequest.AcceptGzip=0
            set pHttpRequest.WriteRawMode=0
            set pHttpRequest.ReadRawMode=0
            set pHttpRequest.OpenTimeout=..Adapter.ConnectTimeout
            set pHttpRequest.Timeout=..Adapter.ResponseTimeout
            set pHttpRequest.WriteTimeout=..Adapter.WriteTimeout
            set pHttpRequest.SocketTimeout=115
            set ..HTTPRequest=pHttpRequest
        }
        set ..HTTPRequest.HttpResponse=tResponse
        do ..HTTPRequest.EntityBody.Write(message)
        set tSC=..HTTPRequest.Post(..Adapter.URL,0,1) if 'tSC {
            set sc=$$$DebugLog("HTTP Write","Error: "_$$$GetErrorText(tSC))
        }
        else {do $$$DebugLog("HTTP Write","Post Sucessful)}
        set response=..HTTPRequest.HttpResponse
        $$$TRACE("HTTP Response Status: "_response.StatusCode)
        if response.StatusCode=200 {
            if $IsObject(response.Data) {
                set message="" while 'response.Data.AtEnd {set message=message_response.Data.Read(,.tSC) if 'tSC quit}
                set pResponse=##class(EnsLib.HL7.Message).ImportFromString(message,.tSC) if 'tSC $$$TRACE("An error occurred creating Respnse Message: "_$system.Status.GetErrorText(tSC))
            }
        }
        else {
            set message="" if $IsObject(response.Data) {set message="" while 'response.Data.AtEnd {set message=message_response.Data.Read(,.tSC) if 'tSC quit}}
            set tSC=$$$ERROR(5001,"HTTP Response Status is "_response.StatusCode_" with Error Text: "_response.ReasonPhrase_" with Content: "_message) quit
        }
    }
    catch ex {
        set tSC=ex.AsStatus()
    }
    if 'tSC,'$IsObject(pResponse) {

  // Here I create a NACK HL7 message if I didn't get a valid response from the remote server. Possibly because the server is down or the 

 //server has crashed. I have a DTL that takes the inbound HL7 request message and transforms it into an HL7 ACK message. I force the ACK 

// Code to "AE" and use the contents of tSC (error status) as the Error Message. Note I make use of the 3rd parameter in the Transform()

// method that allows you to pass in an object containing any properties you want. This is a useful way of getting data into the DTL that

// doesn't exist in either your sourse or target objects.

        set pResponse=##class(EnsLib.HL7.Message).%New(),aux=##class(HPRS.Transformations.CreateNACKDTL.AUX).%New()
        set aux.ACKCode="AE",aux.ACKMessage=$system.Status.GetErrorText(tSC)
        set sc=##class(HPRS.Transformations.CreateNACKDTL).Transform(pRequest,.pResponse,.aux) if 'sc $$$TRACE("Unable to Create NACK Response HL7 Message")
    }
    $$$TRACE("Outcome of HTTP Operation: "_$s(tSC:"Ok",1:"Error: "_$$$GetErrorText(tSC)))
    quit $$$OK

Yours

Nigel Salm
 

I have taken a similar approach. I have a root directory 'Visual Studio Code' and under that I have a number of sub directories. Each of which contains a VS Code workspace file which looks like this:

{
    "folders": [
        {
            "path": "."
        },
        {
            "name": "XXX-XXXX-Interface-GIT",
            "path": "..\\..\\XXX-XXXX-Interface-GIT"
        }
    ],
    "settings": {
        "objectscript.conn.ns": "XXXX-QC",
        "objectscript.conn.password": "**********",
        "objectscript.conn.username": "*******",
        "objectscript.conn.active": true,
        "objectscript.conn.host": "NN.NN.NN.NN",
        "objectscript.conn.docker-compose.internalPort": 57772,
        "objectscript.conn.port": 57772,
        "objectscript.serverSideEditing": true,
        "objectscript.suppressCompileErrorMessages": true
    }
}

I have used 'X''s to obscure my actual Git, Namespace and Server settings and '*''s for my username and password.

Any settings that are universal I put into the VS Code .settings file which is stored in the windows->users->local-> ... directory

Settings such as Objectscript code snippets live in a .vscode file in my windows user settings directory.

This doesn't allow for multiple workspaces to be open at the same time but you can have multiple instanv=ces of VS code open, each one pointing to a different server/namespace where you are working.

Hi

The short answer is yes, you are constrained by the 3.6Mb string size limit and bear in mind that if you are exposing that property to SQL and ODBC you are further constrained by the limitations within ODBC itself. 

I have been giving this whole concept a lot of thought recently. My context is slightly different as I am working in the context of FHIR and JSON and for example you can send in a patient's photo which in JSON has to be represented as a string of Base64Encoded Binary . In principal that photo could be of any size but when the JSON string is converted into a FHIR Patient Object the underlying datatype of the photo.data.value is ultimately derived from a class called %xsd.Base64Binary which is esentially a string and therefore has the size limitation. It doesn't really matter what my underlying persisted datatype is i.e. %GlobalBinaryStream because I am working with the IRIS FHIR Classes as intemediary object classes between my JSON and my underlying persisted classes I have to work with the constraint that the JSON Base64Binary content will fail if it's length exceeds the 3.6Mb (something I wish to address with ISC/WRC at some point).

The alternative of course, to persisting larger data content of something link an image, is to have a url that points to the image location and can be retrieved from the image server for presentation purposes.

So where I know that my images are not going to be that large I read in or write out the base64binary from/to my JSON message but where I know the image is larger such as an x-ray or histo image then that content lives on the appropriate image server as is retirieved  when required for presentation purposes.

Nigel Salm

Hi

Remember that not only messages contribute to the volume of ensemble generated data. If you are using $$$TRACE in your production then this will generate a lot of data in the Ensemble namespace . If you have lots of TRACE statements then this can generate a lot of data.

There is a class method

do ##class(Ens.Purge).PurgeEventLogsByDate({NumberOfDaysToRetainTraceData},.count) There are other methods in this class to purge other management data such as messages

Trace information is stored in the global  ^Ens.Util.LogD and  ^Ens.Util.LogI

Nige;

Hi

Would it be possible to send me your Business Operation code. It is difficult to debug these things without a complete picture of what you are attempting to do,

You can either attach the xml export of the class to this thread or mail it to me at nigel.salm@outlook.com

I get the idea that if the FTP or SMTP servers are not defined you will get the error you get. Is there a reason why they are not specified?

In your code you can always test to see if the ..Adapter.{server} is defined and throw an elegant error rather than wait for the adapter to go through the process of attempting to connect to the the server and then failing,

You can always play with the connection time outs etc so a screen shot of you  business operation production item values would be helpful

Yours

Nigel

My guess is that you need to specify Exception as a Status i.e.:

Exceptuion.AsStatus()

and then your code should read

set pOutput=##class(MyResponse).%New()

set sc=Exception.AsStatus()

set pOutput.errorString=$system.GetErrorText(sc)

but another thought:

If your method quits with tSC (as a %Status) then presumably your method quits with

quit tSC

in which case instead of trowing tSC just quit your Try {} with quit tSC

and in your catch write it as follows:

catch Exception {

   set tSC=Exception .As Status()

}

set pOutput=##class(MyResponse).%New()

set pOutput.errorString=$s(tSC:"",1:$system.Status.GetErrorText(tSC))

quit tSC

Hi

When I was first exploring FHIR support In IRIS for Health I came across a very good Learning Services  Tutorial on the steps to add the extension to the FHIR Resource and then what you have to do to modify sda3 so that the FHIR STU3->SDA3 and the SDA3 -> FHIR STU3. However I did not bookmark the tutorial and despite searching Learning Services I cannot find that  tutorial.

If you know of any tutorial that covers this functionality please can you send me the link

Yours Hopefully

Nigel Salm

Hi

I certainly concur that you do not want to go changing names in any InterSystems Library or System Classes. To be quite honest in my experience export the classes or project that contain all of the classes you want to manipulate and then open that XML export file in NotePad ++ .

Bear in mind that classnames, property names, method names, and storage definitions will all be modified and you should be certain that that is acceptable and desirable otherwise you will need to go through the painful task of working your way through the XML file instance by instance and confirm each replace.

Also bear in mind that if your Class Export references other IRIS classes other than your own application classes then you need to be careful that you don't change an IRIS classname/property name etc... as you may well find that your system no longer works.

Having done the search and replace I suggest that you create a blank database/namespace and import the modified xml file and check any error messages thrown up  by the import process before attempting to import into your proper DEV/UAT database/namespace(s)

Nigel

Hi

Follow this link to the InterSystems Documentation. It details many utilities that can be run from the Cache Terminal. Those that start with a '%' character can be run from any NameSpace. Those without will typically only run in the %SYS NameSpace

https://cedocs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GSTU
 

Bear in mind that many command line utilities have been encapsulated in the  %SYS, %SYSTEM,  %SQL, %Dictionary and other system Packages.

Most of the functionality of the Management Portal is linked to underlying classes and most of them have Methods that can be called programmatically; for example creating resources, roles and users in the security module. Most database and Namespace functionality can be accessed through the %SYS and %SYSTEM packages

If you are looking for specific functions that you would like (if they exist) please list them and I'll see if I can point you in the right direction

Yours

Nigel Salm

Having gone through the pain of Installing Docker for Windows and then installing the InterSystems IRIS for HEALTH 2019.3 image and having got hold of a copy of the 2019.3 Studio I was please when I saw this announcement and excitedly went looking for my 2019.3......exe only to find out there is none and a small note at the end off the announcement to say that 2019.3 InterSystems IRIS and InterSystems IRIS for Heath will only be released in CD form.

Yours

Nigel Salm

Here is a solution I have knocked up that uses the functionality of the Ensemble File Adapter and the HL7 File Operation class

Class User.ExampleHL7FileOperation Extends (Ens.BusinessOperation, EnsLib.HL7.Operation.FileOperation) [ Language = objectscript ]
{ Parameter ADAPTER = "EnsLib.File.OutboundAdapter"; Property Adapter As EnsLib.File.OutboundAdapter; Parameter INVOCATION = "Queue"; Method WriteMessage(pRequest As EnsLib.HL7.Message, Output pResponse As EnsLib.HL7.Message) As %Status
{
        // Initialise your return status variable and a variable called file
        // I use $ztrap to trap code errors though TRY/CATCH would be the more modern approach
        set tSC=$$$OK,$ztrap="Error",file=""
        // Create your file name and append to the Adapter Attribute 'FilePath'
        // if working with UNIX change the next line of code accordingly
        if $e(..Adapter.FlePath,*)'="\" set ..Adapter.FilePath=..Adapter.FilePath_"\"
        set file=..Adapter.FilePath_"ZLOG_FILE_"_pRequest.GetValueAt("MSH::MessageType.MessageStructure",,tSC)_$tr($zdt($h,3),"-: ","")_".txt" if 'tSC quit tSC
        // You can use the appropriate method in the File Outbound Adapter though I have used the OPEN
        // command
        open file:("WNS"):0
        else  set tSC=$system.Status.Error(5001,"Cannot create File: "_file) goto End
        // Invoke the outputDocument method inherited from the class 'EnsLib.HL7.Operation.FileOperation'
        set tSC=..outputDocument(file,pRequest) if 'tSC goto End
End ;
        // Close the file if it exists
        if file'="" close file
        set pResponse=##class(EnsLib.HL7.Message).%New()
        // Populate the response HL7 message as you see fit. Either send back an HL7 ACK or NACK would be
        // the most appropriate
        quit tSC
Error ;
        set $ztrap="",tSC=$system.Status.Error($$$GeneralError,"Code Error: "_$ze) goto End
} XData MessageMap
        {
        <MapItems>
                 <MapItem MessageType="EnsLib.HL7.Message">
                        <Method>WriteMessage</Method>
                </MapItem>
        </MapItems>
        }

}

I have not trested the code but hopefully my coments will set you on the right path to this solution