Marc Mundt · Jun 13, 2019 go to post

Have a look at %System.OBJ.Export():
https://irisdocs.intersystems.com/irisforhealth20192/csp/documatic/%25CSP.Documatic.cls

TESTING>s itm="*.GBL"
TESTING>s file="C:\Projects\export_mm.xml"
TESTING>d $System.OBJ.Export(.itm,.file,,.errors)

Having said that, since this is just for backups you might also consider just using the standard Online Backup utility which will backup all globals in the selected database(s) into a file:

https://irisdocs.intersystems.com/irisforhealth20192/csp/docbook/DocBook.UI.Page.cls?KEY=GCDI_backup#GCDI_backup_methods_online

Marc Mundt · Jun 6, 2019 go to post

Or it looks like I was handling the zero issue manually:

if $L(tHexByte)=1 s tHexByte="0"_tHexByte
Marc Mundt · Jun 6, 2019 go to post

Here's a (very simple) working example of uploading to an AWS S3 bucket. I used essentially the same approach that Cliff originally used to generate the hex. I didn't experience the same problem, but I may have just been lucky and not had any zeroes in the value I'm converting.

<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25" zv="Cache for Windows (x86-64) 2017.2.1 (Build 801_3U)" ts="2018-10-04 08:14:32">
<Class name="Demo.Cloud.Storage.Util">
<Super>%RegisteredObject</Super>
<TimeChanged>64925,29407.276303</TimeChanged>
<TimeCreated>64924,56047.115234</TimeCreated>

<Method name="TestS3Upload">
<ClassMethod>1</ClassMethod>
<Implementation><![CDATA[
    set tAccessKeyId="[Shh... it's a secret]"
    set tSecretAccessKey="[Shh... it's a secret]"
    set tAWSRegion="us-east-1"
    set tAWSService="s3"
    set tHost="s3.us-east-1.amazonaws.com"
    set tPort="443"
    set tBucket="MyBucket12345"
    set tContentType="text/plain; charset=UTF-8"

    set tSSLConfig="AWS"
    set tUseHTTPS=1
    
    set tContentStream=##class(%Stream.TmpCharacter).%New()
    do tContentStream.Write("this is some test content oh yeah!")
    do tContentStream.Rewind()

    set tHorolog=$h
    set tDateKey=$System.Encryption.HMACSHA(256,$ZD(tHorolog,8),"AWS4"_tSecretAccessKey)
    set tDateRegionKey=$System.Encryption.HMACSHA(256,tAWSRegion,tDateKey)
    set tDateRegionServiceKey=$System.Encryption.HMACSHA(256,tAWSService,tDateRegionKey)
    set tSigningKey=$System.Encryption.HMACSHA(256,"aws4_request",tDateRegionServiceKey)
        
    set tHashedPayload=..BytesToHex($System.Encryption.SHAHashStream(256,tContentStream,.tSC))
    
    set tTmpTime=$ZDT(tHorolog,2,7)
    set tHeaderDate=$ZD(tHorolog,11)_", "_$E(tTmpTime,1,11)_" "_$E(tTmpTime,13,*-1)_" GMT"
    set tAmzDate=$TR($zdt(tHorolog,8,7),":","")

    set tKeyName="test_"_tHorolog_".txt"

    set tCanonicalURI="/"_tBucket_"/"_tKeyName
    set tCanonicalURI=$zconvert(tCanonicalURI,"O","URL")

    set tHttpVerb="PUT"
    set tCanonicalHeaders="content-length:"_tContentStream.Size_$c(10)_"content-type:"_tContentType_$c(10)_"host:"_tHost_$c(10)_"x-amz-content-sha256:"_tHashedPayload_$c(10)_"x-amz-date:"_tAmzDate_$c(10)
    set tSignedHeaders="content-length;content-type;host;x-amz-content-sha256;x-amz-date"
    set tCanonicalRequest=tHttpVerb_$c(10)_tCanonicalURI_$c(10,10)_tCanonicalHeaders_$c(10)_tSignedHeaders_$c(10)_tHashedPayload
    set tStringToSign="AWS4-HMAC-SHA256"_$c(10)_$TR($zdt(tHorolog,8,7),":","")_$c(10)_$zd(tHorolog,8)_"/"_tAWSRegion_"/"_tAWSService_"/aws4_request"_$c(10)_..BytesToHex($System.Encryption.SHAHash(256,tCanonicalRequest))
    set tSignature=..BytesToHex($System.Encryption.HMACSHA(256,tStringToSign,tSigningKey))    
    set tAuthHeader="AWS4-HMAC-SHA256 Credential="_tAccessKeyId_"/"_$zd(tHorolog,8)_"/"_tAWSRegion_"/"_tAWSService_"/aws4_request,SignedHeaders="_tSignedHeaders_",Signature="_tSignature

    set tHReq=##class(%Net.HttpRequest).%New()
    set tHReq.Server=tHost
    set tHReq.Port=tPort
    set tHReq.SSLConfiguration=tSSLConfig
    set tHReq.Https=tUseHTTPS

    do tHReq.SetHeader("Authorization",tAuthHeader)
    do tHReq.SetHeader("Content-Type","text/plain")
    do tHReq.SetHeader("Content-Length",tContentStream.Size)
    do tHReq.SetHeader("Host",tHost)
    do tHReq.SetHeader("x-amz-content-sha256",tHashedPayload)
    do tHReq.SetHeader("x-amz-date",tAmzDate)

    do tHReq.EntityBody.CopyFrom(tContentStream)

    //do tHReq.OutputHeaders()

    s tSC=tHReq.Put(tCanonicalURI)
    do $System.OBJ.DisplayError(tSC)
    
    write "Status Code:",tHReq.HttpResponse.StatusCode,!
    
    //Do tHReq.HttpResponse.OutputToDevice()
    
    if $isobject(tHReq.HttpResponse.Data) {
        s ^zresp=tHReq.HttpResponse.Data.Read(1000000)
    } else {
        s ^zresp=tHReq.HttpResponse.Data
    }
]]></Implementation>
</Method>

<Method name="BytesList">
<ClassMethod>1</ClassMethod>
<FormalSpec>pBytes:%String</FormalSpec>
<ReturnType>%String</ReturnType>
<Implementation><![CDATA[
    s l=$LISTFROMSTRING(pBytes," ")
    f i=1:1:$LL(l)  s st=$G(st)_$CHAR($ZHEX($LISTGET(l,i)))
    return st
]]></Implementation>
</Method>

<Method name="BytesToHex">
<ClassMethod>1</ClassMethod>
<FormalSpec>pBytes:%String</FormalSpec>
<ReturnType>%String</ReturnType>
<Implementation><![CDATA[
    set tHex=""
    
    for i=1:1:$L(pBytes) {
        set tHexByte=$ZHEX($ASCII($E(pBytes,i)))
        if $L(tHexByte)=1 s tHexByte="0"_tHexByte
        set tHex=tHex_tHexByte
    }
    
    return $ZCONVERT(tHex,"L")
]]></Implementation>
</Method>
</Class>
</Export>
Marc Mundt · Apr 12, 2019 go to post

Yes, you can loop through items in your source document and generate one HL7 message for each. 

In this case the looping would be done in a business process (BPL), and you would call the transformation once for each outbound HL7 message.

Marc Mundt · Apr 10, 2019 go to post

Have you tried setting "DefCharEncoding" to UTF-8 in the settings for EnsLib.HL7.Operation.HTTPOperation?

Here are the details from the popup-help. Especially note the highlighted line. Using "!UTF-8" will cause the operation to ignore what's in MSH:18 and always use UTF-8.

Default Character Encoding to use when reading or writing HL7 messages.
If MSH field 18 (Character Set) is empty, this encoding will be used. Choices you can use for this setting include:Native: Use the default character encoding of the installed locale of the IRIS server.
latin1: The ISO Latin1 8-bit encoding. This is the default.
ISO-8859-1: The ISO Latin1 8-bit encoding.
UTF-8: The Unicode 8-bit encoding.
Unicode: The Unicode 16-bit encoding (Little-Endian).
UnicodeBig: The Unicode 16-bit encoding (Big-Endian).
Any other NLS definitions installed on this IRIS server.
@<ttable>: <ttable> means a raw InterSystems character translation table name. A prefix of '@' means to use the named table.
Putting ! before the encoding name will force the use of the named encoding and will ignore any value found in MSH:18.
Marc Mundt · Apr 10, 2019 go to post

Hi Joseph,

I developed something like this for a POC last year. It includes wizards to generate Business Services and Business Operations and related message objects based on a source query or stored procedure.

It's not currently in a state where I can share it, but I'm planning to eventually clean it up and post it on Open Exchange. I'll let you know when I do.

-Marc

Marc Mundt · Feb 19, 2019 go to post

What about setting "Reply Code Actions" to "E=D" and enabling "Alert on Error"?
From the reply code actions help text:
"D - Disable the Process, log an error and restore the original incoming message to the front of the Process's queue."

Marc Mundt · Jan 25, 2019 go to post

For current versions, the approach I've seen customers use is to create a business operation using the HL7 or XML outbound file adapter. In Message Viewer you can then "resend" the messages you want to the new business operation.

I'm happy to say that we've added a way to download multiple messages directly from Message Viewer. This hasn't been released yet but will be soon. For more details on this and other upcoming features, have a look at this presentation.

Marc Mundt · Jan 22, 2019 go to post

Hi Guillaume,
Here's some rough code showing how to use executeParametersBatch. I put this together from a few other working examples I had but I haven't tested this actual code and it may have bugs.
As always, this is provided as sample code only and is not meant for production use :)
-Marc
 

set pSQLStatement="INSERT INTO Test.Table (Field1,Field2) VALUES (?,?)"

// JDBCGwy is an instance of the JDBC Gateway object. EnsLib.SQL.OutboundAdapter instantiates this automatically and stores a reference to it in ..%Connection.%JGProxy

// Prepare the SQL statement
set pHS=JDBCGwy.prepareStatement(ConnHandle,pSQLStatement)

// executeParametersBatch expects tArgs to be a $LIST, with the following format:
//     ParamCount, ParamSets, Type1, Param1, Type2, Param2, Type3, Param3, Type11,Param11… TypeNN,ParamNN
// 
// ParamCount is the number of parameters the query expects (in this example 2) 
// ParamSets is the number of rows we will be inserting in this batch
// Type1, Type2, ..., TypeN is an integer indicating the JDBC data type for the corresponding Param value (e.g. Param1, Param2, ..., ParamN)
// Param1, Param2, ..., ParamN is the value for the query parameter

set $LIST(tArgs,1)=2  // The query has two parameters ("?") in it
set $LIST(tArgs,2)=3  // We will insert 3 rows in this batch

// Row 1 -------------------------------------------------------------

//    Set the value for the first column (Field1VarChar)
set $LIST(tArgs,3)=12 // The JDBC data type for varchar is 12
set $LIST(tArgs,4)="String value 1" // Value for column Field1VarChar

//    Set the value for the second column (Field2Integer)
set $LIST(tArgs,5)=4 // The JDBC data type for integer is 4
set $LIST(tArgs,6)=7 // Value for column Field2Integer

// Row 2 -------------------------------------------------------------

//    Set the value for the first column (Field1VarChar)
set $LIST(tArgs,7)=12 // The JDBC data type for varchar is 12
set $LIST(tArgs,8)="String value 2" // Value for column Field1VarChar

//    Set the value for the second column (Field2Integer)
set $LIST(tArgs,9)=4 // The JDBC data type for integer is 4
set $LIST(tArgs,10)=123 // Value for column Field2Integer

// Row 3 -------------------------------------------------------------

//    Set the value for the first column (Field1VarChar)
set $LIST(tArgs,11)=12 // The JDBC data type for varchar is 12
set $LIST(tArgs,12)="String value 3" // Value for column Field1VarChar

//    Set the value for the second column (Field2Integer)
set $LIST(tArgs,13)=4 // The JDBC data type for integer is 4
set $LIST(tArgs,14)=54 // Value for column Field2Integer


// Perform the batch insert
// tResultCodes is a $LIST of integers indicating success/failure for each row in the batch
set tResultCodes=JDBCGwy.executeParametersBatch(pHS,tArgs)
 
Marc Mundt · Jan 15, 2019 go to post

You can stop a business process completely (along with all other components) by stopping the production:
https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KE…
To only stop a specific business process, you can disable that specific component:
https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KE…
You can also view the status of and manage individual jobs that are doing processing for the business process:
https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KE…

Marc Mundt · Jan 7, 2019 go to post

In the case of moving from AIX to Windows, you're not just changing the OS but also changing the platform (IBM Power to Intel).

As Dmitry said, there's no relation to the OS but there is an important issue when changing platforms: endianness. AIX on Power is big-endian while Windows/Linux/etc. on Intel are little-endian.

The upshot is: when moving a cache.dat from AIX to Windows you'll need to run an included utility to convert it from big-endian to little-endian.

Marc Mundt · Nov 2, 2018 go to post

This won't help you in the short term, but upcoming versions will include a nice feature to export messages from the message search page. For more details, have a look at this presentation from this year's InterSystems Global Summit.
https://learning.intersystems.com/course/view.php?id=1015
For the short term, most people use the suggestion from @Julian Matthews of adding a new file out business operation and resending the messages to it.

Marc Mundt · Oct 24, 2018 go to post

Joao, just wanted to check with you... did it work after adding quotation marks?

Marc Mundt · Oct 18, 2018 go to post

I'm assuming that the source message includes the email address in PID:13.4 and that your DTL is set to "copy" mode?
If so, you can add a "set" action to set the value of PID:13.4 to an empty string ("").

Marc Mundt · Oct 16, 2018 go to post

Some business operations that use EnsLib.File.OutboundAdapter will pull the value for %f from a property in the request object. This would allow you to set that property in a DTL before sending it to the business operation.

But, it looks like you're using EnsLib.XML.Object.Operation.FileOperation, which doesn't do this. It uses the class name of the inbound object as the value for %f.

To create a filename programmatically with EnsLib.XML.Object.Operation.FileOperation, you can create a custom class that extends EnsLib.XML.Object.Operation.FileOperation and override the OnMessage() method. 

The relevant line in the standard OnMessage method is this. You can replace this with your custom logic:

    // Create output filename using the class name of the persistent class as the base.
    Set tFilename=..Adapter.CreateFilename($classname(pRequest),..Filename)
Marc Mundt · Sep 20, 2018 go to post

The documentation for %Net.HttpRequest has some sample code that should be useful:

From that page:

In order to send parameters on the URL, i.e. when you see a URL like

http://www.demo.com/page.html?PARAM1=TEST&PARAM2=HELLO

You use the SetParam method to add these parameters one by one. For example to get the index page from 
Documatic you do:

    Set httprequest=##class(%Net.HttpRequest).%New()
    Set httprequest.Port=1972
    Do httprequest.SetParam("PAGE","INDEX")
    Do httprequest.Get("/csp/docbook/%CSP.Documatic.cls")
    Do httprequest.HttpResponse.OutputToDevice()

You may also pass the query parameters on the Get call directly too as long as they are correctly escaped.
Marc Mundt · Sep 20, 2018 go to post

In addition to setting FetchSize, we can also improve the speed with an optimization to how EnsLib.SQL.GatewayResultSet fetches rows for large result sets.
This optimization is planned to be included in a future product version, but it is possible to do this in current versions with custom code. What version of Ensemble are you using?
Guillaume, do you know who the InterSystems Sales Engineer is for your company? We should discuss this by email and we can get you some sample code.

Marc Mundt · Sep 19, 2018 go to post

File operations pull the value for %f from the "Source" property of the EnsLib.EDI.XML.Document object. In your code sample you're creating a new EnsLib.EDI.XML.Document and copying the output from your XSLT transformation into it, but you're not setting Source to anything. As an experiment, try setting Source to a value and confirm that your output file is given that name.

set xmlResultDoc.Source="MyFileName.xml"


If you want %f to use the same filename as the original input file then you'll want to grab the Source value from your original object that was created by the business service and copy it to xmlResultDoc.Source.

Marc Mundt · Sep 13, 2018 go to post

The "Context" and "Expression" should be wrapped in quotation marks.

Marc Mundt · Sep 13, 2018 go to post

Looking at the generated code, this is probably not possible.
Each context variable becomes a standard property in an auto-generated context class. The "Default Value" becomes the "InitialExpression" attribute of the property. This means the default is assigned by the underlying object framework upon instantiation of the context object rather than by the BPL engine itself.

Class Test2.NewProcess1.Context Extends Ens.BP.Context [ ClassType = persistent, CompileAfter = Test2.NewProcess1, GeneratedBy = Ens.BPL.Compiler.CLS, ProcedureBlock ]
{

Property testvar1 As %String(MAXLEN = 50) [ InitialExpression = "blah" ];

}

Marc Mundt · Sep 13, 2018 go to post

Here's an example of what Carlos is suggesting with XECUTE:

ClassMethod CompareValues(val1, val2, operator) as %Boolean {
    set testStatement="set testResult=(val1 "_operator_" val2)"
    XECUTE testStatement
    quit testResult
}

[edited code to fix a typo]

Marc Mundt · Jul 26, 2018 go to post

Have a look at the separators and framing settings in EnsLib.HL7.Operation.FileOperation. The sixth item in Separators is the segment terminator (carriage return in standard HL7) while Framing lets you change the message terminator (line feed in standard HL7). You can change these to non-standard values if needed.
From the separators documentation linked above:

Separators
 HL7 separator characters to use in the outgoing message. If you leave this field blank, the default is:
 
|^~\&
 Basics
 An HL7 message uses special characters to organize its raw contents. These characters may vary from one clinical application to another. For this reason, the HL7 standard requires that each HL7 message list the five specific characters that it is using as separators at the start of the MSH segment, in order from left to right:
  1.  Field separator (FS)
  2.  Component separator (CS)
  3.  Repetition separator (RS)
  4.  Escape character (ESC)
  5.  Subcomponent separator (SS)
A sixth character, the segment terminator character, is not specified in MSH and is generally assumed to be a carriage return (ASCII 13).
Details
 For Separators, you must supply a string of characters which Ensemble assigns to HL7 separators in left to right order: FS, CS, RS, ESC, SS as described in the previous list.
 Beyond positions 1 through 5 of the Separators string, you can supply additional characters to override the default segment terminator character, the carriage return (ASCII 13). After position 5, use \r for the carriage return (ASCII 13) and \n for the line feed (ASCII 10).
 You can use \x in positions 1 through 5 if you need to specify segment terminators in positions 6 and higher but want your output messages to use fewer than 5 separators. Separators designated by \x in positions 1 through 5 are not used. The purpose of \x is simply to extend the length of the list of separators so that position 6 is interpreted correctly as the first segment terminator.
Marc Mundt · Jun 27, 2018 go to post

I see an issue in the first piece of code you posted.

At the top of the method you do this:
set pInput=pRequest.FileStream
But when you write the stream to the file, you do this:
set status=..Adapter.PutStream(..Filename, pInput.Stream)
 
I don't know for certain what type of object pRequest is or pRequest.FileStream, but in the service you posted it looks like pRequest.FileStream is a %Stream.Object.

Method OnProcessInput(pInput As %Stream.Object, Output pOutput As %RegisteredObject) As %Status
...
set pt.filestream=pInput

If pRequest.FileStream is a %Stream.Object, then it won't have a Stream property and passing pInput.Stream to PutStream should fail. Try changing this to:
set status=..Adapter.PutStream(..Filename, pInput)

Marc Mundt · Jun 27, 2018 go to post

The method signature for OnMessage usually only has two parameters: the inbound request and a response to return.
In your method you have three parameters and two of them seem to be inbound requests. Can you clarify?

Method OnMessage(pREs As TestingEnvironment.ECGTrace.TEST.FSMREQ, pRequest As Ens.StreamContainer, Output pResponse As %Persistent) As %Status

Marc Mundt · Jun 25, 2018 go to post

IIRC "#" means that the character doesn't exist in the chosen font. The default font may not be very complete. You might want to explicitly set the font you're using to one you know contains Cyrillic characters.
Also, in your Zen Report class you can set the encoding as a parameter. This may not be necessary since it should default to UTF-8:
Parameter ENCODING="ISO-8859-1";