As part of the encryption negotiation process, there's an exchange of supported cypher suites between the client and server. If there's no match, no connection can be established. No need to force a specific cypher site; all available should be presented by the client during connection negotiation.

If upgrading to a current version of HealthShare/Health Connect is not an option, you could script the transfers outside of the production (batch/powershell/Python/Perl script running under Windows' Scheduler or called from ObjectScript in a Scheduled Task via $ZF(-100) ) and then use a File service/operation to pick them up for processing or drop them off for delivery.

I've worked with a number of integration solutions over the last few decades and not a single one of them would work with this vendor's perverse interpretation of HL7 communications out-of-the-box.

Health Connect / HealthShare can accommodate pretty much anything if you're willing to dip your toes into ObjectScript and write custom services and operations. But the OOB classes won't handle the vendor's requirements without subclassing/extending.

Acknowledgements

There are certainly variances in the types of acknowledgements received and even cases where acknowledgements are received out-of-band (Sunrise Clinical Manager, I'm looking at you). I've even seen implementations where multiple HL7 messages are sent, streamed in a batch, with envelope characters wrapped around the whole batch rather than the individual messages. In that case no ACK was expected. I wrote a custom service to handle that one.

Fundamentally, Health Connect is a request/response messaging system and violating that design presents some interesting challenges. Receiving the ACKs back asynchronously over the same connection is theoretically possible, but I wouldn't encourage the vendor to work that way ... every single future customer will be problematic for them.

Message Control IDs

I've never seen an engine that increments the message control ID automatically, although it's something that can be coded/customized in multiple solutions. The message control ID is defined by the sending application, not the middleware; setting the MSH:10 in the engine is usually an extraordinary measure.

HL7.org defines it as such:

This field contains a number or other identifier that uniquely identifies the message.

If the same message was re-sent with a different MSH:10 value, it would not be uniquely identified. In my opinion, at least.

Depends on your platform (although that may have changed since the last time I configured an LDAP setup).

With Caché/IRIS running on *nix variants, the only option is STARTTLS, which is encrypted but uses the "standard" port 389. With Windows, I believe "LDAP over SSL" (aka LDAPS) is also an option, on port 686 by default.

Both will require that whatever certificate is served is valid for the load balancer. This is usually accomplished via a certificate Subject Alternative Name value.

Not with this code, no. I interpreted your original request literally, assuming that you wanted to send the email directly from within a custom service.

If you wish to have an Email operation in the production that handles the delivery of messages from routing rules, that's a bit more work to create, but in the end more versatile and easier to support.

If you don't care about having an operation, you could create a class that extends Ens.Rule.FunctionSet that wraps the original class in such a  way that you can send email from a rule:
 

Class User.Util.FunctionSet Extends Ens.Rule.FunctionSet
{
ClassMethod SendEmail(pToAddr As %String, pSubject As %String, pMessageBody As %String) As %Status
{
	Set tMail=##class(User.Mail).%New()
	Set tSC = tMail.Send(pToAddr, pSubject, pMessageBody)
}
}

You could then call it from a rule (the assign simply lets you call it and optionally do something with the returned status):

@Eduard Lebedyuk is correct (yeah, he's always correct 😁), you can't use a variable for the target in a business rule.

You can do this in a custom BP (COS or Python) or a BPL-based BP, though. The BPL <call> action specifically supports a context variable as a destination:

The variable would be assigned the name of the BH to send to prior to invoking the call.

It's not clear from your post whether you're using any of the healthcare-related variants of the InterSystems suite.

If you're using HealthShare, Health Connect or IRIS for Health, support is included for receiving HL7 messages and storing them via multiple mechanisms. TCP/IP MLLP, HTTP, and file based services are all supported natively.

When configuring a Business Service to receive HL7 messages via TCP/IP MLLP, you would select the EnsLib.HL7.Service.TCPService class and configure the Message Schema Category to "2.5" or "2.5.1" depending on your specific HL7 version. You would also need to configure the port on which to receive the messages, the target Business Process or Operation to act on them, and possibly a few other settings.

Messages received through that service will then be inserted into the Interoperability message store, regardless of HL7 message type; message headers are created to provide tracking/status information and the messages are databased.

If your need is to index and retrieve those messages based on content (patient name, account number, gender, etc), there are additional steps to fetch the specific data elements needed from the messages themselves and populate associated fields in a database structure that addresses those needs. That's something you would need to design; you would populate it using interoperability production components tailored to your filtering requirements and database design.

Can you try the code I posted above, substituting appropriate paths/filenames in the calls to the LinkToFile() methods? Any file will do for the in stream, as long as file/directory permissions permit. This at least would tell us whether the issue is with the key file or the JWT you're attempting to encrypt.

I've tried this on I4H 2023.1 and Health Connect 2021.1.2 and the RSASHASign() method has not failed to generate a signature unless the key was passphrase-protected or not readable (due to file ownership/permissions) by the process opening it.

Web services normally use an HTTP status code; for example, an ACK would be 200 OK for REST/HTTP and would be available through the %Net.HttpResponse Object in the StatusCode/StatusLine properties. SOAP usually provides some sort of payload along with the status code, and that would be found in the Data property. The type of response would likely be identified in the source/target system's WSDL for the SOAP interface.

This is something I wrote a long time ago; it extracts all business hosts and their settings. I've learned some things since I wrote it and would probably do a few things differently these days. It should be enough to give you some ideas, though ...

ClassMethod GetConfigs(pProduction As %String = {$G(^Ens.Runtime("Name"),$G(^Ens.Suspended,$G(^Ens.Configuration("csp","LastProduction"))))}, pFile As %String = {$System.Util.GetEnviron("HOME")_"/"_$NAMESPACE_"_hostconfigs.csv"}) As %Status
{
    Set tPrd = ##class(Ens.Config.Production).%OpenId(pProduction)
    Set tOut = ##class(%File).%New()
    Set tOut.Name = pFile
    Set tSC = tOut.Open("RWN")
    if '$$$ISERR(tSC)
    {
        Set tSC = vOut.WriteLine("""Type"",""Name"",""ClassName"",""Adapter"",""Enabled"",""ConfigName"",""ConfigValue""")
    }
    Quit:$$$ISERR(tSC) tSC
    If $ISOBJECT(tPrd)
    {
        For i=1:1:tPrd.Items.Count()
        {
            Set tHost = tPrd.Items.GetAt(i)
            Set tName = tHost.Name
            Set tClassName = tHost.ClassName
            Set tType = $CASE(tHost.BusinessType(),0:"Unknown",1:"Service",2:"Process",3:"Operation",4:"Actor",:"Huh?")
            Set tAdapter = $CLASSMETHOD(tClassName,"%GetParameter","ADAPTER")
            Set tEnabled = tHost.Enabled
            Set tCategory = tHost.Category
            Set tLine = """"_tType_""","""_tName_""","""_tClassName_""","""_tAdapter_""","""_tEnabled_""","""
            Do tOut.WriteLine(tLine_"Category"","""_tCategory_"""")
            For l=1:1:tHost.Settings.Count()
            {
                Set tCfg = tHost.Settings.GetAt(l)
                Set tCfgName = tCfg.Name
                Set tCfgVal = tCfg.Value
                Set tSC = vOut.WriteLine(tLine_tCfgName_""","""_tCfgVal_"""")
                Return:$$$ISERR(tSC) tSC
            }
        }
        Do tOut.Close()
    }
    Else
    {
        Return $$$ERROR(0,"Production Not Found in this namespace")
    }
    Return $$$OK
}

That should probably be a choice for completeness' sake.

But you can create an ObjectScript class by right-clicking the server or one of its packages in the Explorer pane and entering the package/class name with a .cls extension. Use slashes rather than periods as package/class delimiters (if you use periods, it may display differently in Explorer until you refresh the window).

So I forgot that there's a Pad() method in the DTL function list that would likely work better for your purposes than $EXTRACT() and $JUSTIFY(). You can use it to zero or space fill the fields to the required width. The first argument is the value to pad, the 2nd the width (positive numbers for pad right, negative for left), and the 3rd is the pad character to fill with.

Your update to the requirements is incomplete; it doesn't specify what, if anything, goes in the 2nd (and subsequent) row(s) of the output after the ItemCodeExternal.Identifier value, whether the fractional value is in the Quantity field is right or left justified zero-filled, or whether the UnitofMeasure and DateNeeded values are padded to make line length consistent across all records.

Here's an example of what it might look like and will need to be adjusted to accommodate your vendor's spec:

The code rules that write the records to the stream would need to be adjusted to eliminate the "|" delimiters and insert the renamed/added variables:

This should get you to where you need to be.

I went ahead and created a DTL that appears to do what you requested and does not require a custom File Operation to work; It assumes you're using EnsLib.File.PassthroughOperation as the outbound operation class.

The filename is created using the value set for target.OriginalFilename in Ens.StreamContainer in the DTL, so you could base it on something from the HL7 message itself or just set it to a static value (as I've done). You can use date/time tokens in the outbound operation's File Name field to aggregate multiple messages per file, or just let it create uniquely named files for each message with the default pattern.

Here's the DTL Configuration:

And the rules:

To test, I created a HL7 file with repeating ORC groups based on the sample provided in your post, but the DTL will work whether it's repeating or not:

The Filename pattern I used in the outbound operation:

This file was created:

And contained this output:

Hope this helps.

Well I guess there IS a setting (thanks, @Eduard Lebedyuk!) laugh

The parameter Undefined specifies the behavior when ObjectScript attempts to fetch the value of a variable that has not been defined. The value of Undefined may be 0, 1, or 2:

  • 0 - Always throw an <UNDEFINED> error. (default)
  • 1 - If the undefined variable has subscripts, return a null string, but if the undefined variable is single-valued, throw an <UNDEFINED> error.
  • 2 - Always return a null string.

You can change that setting in System Administration | System Configuration | Additional Settings | Compatibility.

Not sure what version of Caché or IRIS you're on; for future reference it's helpful to include that information. In IRIS 2021.2, you can do this from the IRIS SQL Shell:

JEFF>do $system.SQL.Shell()
SQL Command Line Shell
----------------------------------------------------
The command prefix is currently set to: <<nothing>>.
Enter <command>, 'q' to quit, '?' for help.

[SQL]JEFF>>set displaypath /home/jeff/tmp/
displaypath = /home/jeff/tmp/

[SQL]JEFF>>set displayfile sqlout
displayfile = sqlout

[SQL]JEFF>>set displaymode csv
displaymode = csv

[SQL]JEFF>>set selectmode display
selectmode = display

[SQL]JEFF>>select top 100 * from Ens_Util.Log
13.     select top 100 * from Ens_Util.Log

/home/jeff/tmp/sqlout.csv
/home/jeff/tmp/sqloutMessages.txt

statement prepare time(s)/globals/cmds/disk: 0.0002s/6/831/0ms
          execute time(s)/globals/cmds/disk: 0.0035s/467/20822/0ms
                          cached query class: %sqlcq.JEFF.cls115
---------------------------------------------------------------------------

The default delimiter is comma, but you can change that. For example, the tab character:

[SQL]JEFF>>set displaydelimiter = $C(9)