Eduard Lebedyuk · Dec 11, 2024 go to post

1 outputs but does not send the request, so just call Send with 1 first, then with 2 to get both request and response.

Eduard Lebedyuk · Nov 11, 2024 go to post

Quick question about the port. Is it the standard port the IRIS instance is running on?

Yes, 1972 is a default Super Server port.

I do not need an external language server connection on the host?

External language servers talk to Super Server, so in a case of IRIS to IRIS we can just talk to Super Server directly.

Eduard Lebedyuk · Nov 8, 2024 go to post

Convert xml to FHIR object:

Set payload=##class(HS.FHIR.DTL.vR4.Model.Resource.Organization).FromXML(FHIRxmlStream)
Eduard Lebedyuk · Nov 8, 2024 go to post

Here's how to do it (sample code to transfer files over iris connection):

/// Get IRIS connection object/// set iris = ##class().GetIRIS()ClassMethod GetIRIS() As%Net.DB.Iris
{
    Set host = "host"Set port = 1972Set ns = "%SYS"Set user = "_SYSTEM"Set pass = "***"Set connection = ##class(%Net.DB.DataSource).CreateConnection(host, port, ns, user, pass)
    Set iris = connection.CreateIris()
    Quit iris
}

/// Transfer one file from sourceFile to targetFile on iris connection.ClassMethod Transfer(iris As%Net.DB.Iris, sourceFile As%String, targetFile As%String) As%Status
{
    Set sc = $$$OKTry {
        Set stream = ##class(%Stream.FileBinary).%New()
        Do stream.LinkToFile(sourceFile)
        Set var = "%stream"Do iris.ClassMethodVoid($classname(), "InitStream", var, targetFile)
        While 'stream.AtEnd {
            Set chunk = stream.Read($$$MaxStringLength-1000)
            Do iris.ClassMethodVoid($classname(), "WriteStream", var, chunk)
        }
        Do iris.ClassMethodVoid($classname(), "SaveStream", var, ##class(%File).Attributes(sourceFile))
    }
    Catch ex{
        Do##class(%SYS.System).WriteToConsoleLog("SuperServer Copy failure in Transfer:" _ ex.DisplayString())
        Throw ex

    }

    Quit sc
}

/// Initialize stream for subsequent write requests and place it in var./// var must be a global variable (start form %) /// file is created or truncated if already existsClassMethod InitStream(var As%String, file As%String)
{
    Try {
        Do##class(%File).Truncate(file)
        Set stream = ##class(%Stream.FileBinary).%New()
        Do stream.LinkToFile(file)
        Set @var = stream
    } Catch ex{
        Do##class(%SYS.System).WriteToConsoleLog("SuperServer Copy failure in InitStream: " _ ex.DisplayString())
        Throw ex
    }
}

/// Wrile string into a stream initialized by InitStream ClassMethod WriteStream(var As%String, string As%String)
{
    Try {
        Do$method(@var, "Write", string)
    } Catch ex{
        Do##class(%SYS.System).WriteToConsoleLog("SuperServer Copy failure in WriteStream: " _ ex.DisplayString())
        Throw ex
    }
}

/// Save stream initialized by InitStream. /// Optionally sets file attributes.ClassMethod SaveStream(var As%String, attributes As%String = "")
{
    Try {
        Set sc = $method(@var, "%Save")
        Set file = $property(@var, "Id")
        Kill @var
        Do:attributes'=""##class(%File).SetAttributes(file, attributes)
    } catch ex {
        Do##class(%SYS.System).WriteToConsoleLog("SuperServer Copy failure in Savetream: " _ ex.DisplayString())
        Throw ex
    }
}

Use ClassMethodValue to get a scalar value back. Use json for complex type transfer as objects are not supported. There are other methods corresponding to APIs in other languages.

Also please note that this class (technically only the callee *Stream methods but save yourself a headache and just copy the entire class) needs to be present on both nodes.

Finally, remember that callee methods must produce no stdout/stderr writes, since the io is bound to the iris connection itself and it cannot disambiguate stdout.

Eduard Lebedyuk · Sep 20, 2024 go to post

Great article, Ron!

ICD, DSM, SNOMED, and other classifiers on Coruscant must be crazy.

Eduard Lebedyuk · Sep 20, 2024 go to post

I think projecting as an attribute is enough. Here's an example:

Class Utils.Message Extends (%RegisteredObject, %XML.Adaptor)
{

Parameter XMLNAME = "ID";Property scope As%String(XMLPROJECTION = "ATTRIBUTE");

}

DTL:

Class Utils.DTL Extends Ens.DataTransformDTL
{

XData DTL [ XMLNamespace = "http://www.intersystems.com/dtl" ]
{
<transform sourceClass='Ens.Request' targetClass='Utils.Message' create='new' language='objectscript' >
<assign value='"Message"' property='target.scope' action='set' />
</transform>
}

/// do ##class(Utils.DTL).Test()ClassMethod Test()
{
	set source = ##class(Ens.Request).%New()
	#dim target As Utils.Message
	set sc = ..Transform(source, .target)
	do target.XMLExportToString(.xml)
	w xml, !
}

}

Results in:

>do##class(Utils.DTL).Test()

<ID scope="Message"></ID>
Eduard Lebedyuk · Sep 20, 2024 go to post

My recommended approach is to call routines in a silent mode if at all possible, or to do minimal modifications to add silent mode. But here's how you can work with read using input redirection:

ClassMethod Test() [ ProcedureBlock = 0 ]
{
    set tOldIORedirected = ##class(%Device).ReDirectIO()
    set tOldMnemonic = ##class(%Device).GetMnemonicRoutine()
    set tOldIO = $iotry {
        set str=""//Redirect IO to the current routine - makes use of the labels defined belowuse$io::("^"_$ZNAME)

        //Enable redirectiondo##class(%Device).ReDirectIO(1)

        setx = ..MyLegacyRoutine()
    } catch ex {
        do ex.Log()
    }

    //Return to original redirection/mnemonic routine settingsif (tOldMnemonic '= "") {
        use tOldIO::("^"_tOldMnemonic)
    } else {
        use tOldIO
    }
    do##class(%Device).ReDirectIO(tOldIORedirected)
    
    w !,"x is: ",x,!
    w"Routine wrote to device: ", str

    //Labels that allow for IO redirection//Read Character
rchr(time)  quit"a"//Read a string
rstr(len,time) quit"xyz"//Write a character - call the output label
wchr(s)      do output($char(s))  quit//Write a form feed - call the output label
wff()        do output($char(12))  quit//Write a newline - call the output label
wnl()        do output($char(13,10))  quit//Write a string - call the output label
wstr(s)      do output(s)  quit//Write a tab - call the output label
wtab(s)      do output($char(9))  quit//Output label - this is where you would handle routine device output.//in our case, we want to write to str
output(s)    set str=str_s   quit
}

ClassMethod MyLegacyRoutine()
{
    read"Input x: ",xwrite"Hello!"returnx
}

}

 It outputs:

x is: xyz
Routine wrote to device: Input x: Hello!
Eduard Lebedyuk · Sep 19, 2024 go to post

The easiest way would be to run on the first day of a month and send results for a previous month.

Another way you can do it is to create a schedule which runs 9:00 am to 9:30 am every day (or 28-31) and checks if this is a last day of a month before doing anything.

Schedule is limited to Max String length, so you can also generate a very long schedule once. For example this code would produce a schedule for the next 10 years:

/// date - start date. Must be the last day of the month./// months - how many months to generateClassMethod Test(date = "2024-09-30", months = 120)
{
	while$i(months,-1)>=0 {
		set year = $system.SQL.DATEPART("year", date)
		set month = $tr($j($system.SQL.DATEPART("month", date), 2), " ", 0)
		set day = $system.SQL.DATEPART("day", date)
		write$$$FormatText("START:%1-%2-%3T09:00:00,STOP:%1-%2-%3T09:30:00,", year, month, day)
		set date = $system.SQL.DATEADD("day", -1, $system.SQL.DATEADD("month", 1, $system.SQL.DATEADD("day", 1, date)))
	}
}
 

Schedule

START:2024-09-30T09:00:00,STOP:2024-09-30T09:30:00,START:2024-10-31T09:00:00,STOP:2024-10-31T09:30:00,START:2024-11-30T09:00:00,STOP:2024-11-30T09:30:00,START:2024-12-31T09:00:00,STOP:2024-12-31T09:30:00,START:2025-01-31T09:00:00,STOP:2025-01-31T09:30:00,START:2025-02-28T09:00:00,STOP:2025-02-28T09:30:00,START:2025-03-31T09:00:00,STOP:2025-03-31T09:30:00,START:2025-04-30T09:00:00,STOP:2025-04-30T09:30:00,START:2025-05-31T09:00:00,STOP:2025-05-31T09:30:00,START:2025-06-30T09:00:00,STOP:2025-06-30T09:30:00,START:2025-07-31T09:00:00,STOP:2025-07-31T09:30:00,START:2025-08-31T09:00:00,STOP:2025-08-31T09:30:00,START:2025-09-30T09:00:00,STOP:2025-09-30T09:30:00,START:2025-10-31T09:00:00,STOP:2025-10-31T09:30:00,START:2025-11-30T09:00:00,STOP:2025-11-30T09:30:00,START:2025-12-31T09:00:00,STOP:2025-12-31T09:30:00,START:2026-01-31T09:00:00,STOP:2026-01-31T09:30:00,START:2026-02-28T09:00:00,STOP:2026-02-28T09:30:00,START:2026-03-31T09:00:00,STOP:2026-03-31T09:30:00,START:2026-04-30T09:00:00,STOP:2026-04-30T09:30:00,START:2026-05-31T09:00:00,STOP:2026-05-31T09:30:00,START:2026-06-30T09:00:00,STOP:2026-06-30T09:30:00,START:2026-07-31T09:00:00,STOP:2026-07-31T09:30:00,START:2026-08-31T09:00:00,STOP:2026-08-31T09:30:00,START:2026-09-30T09:00:00,STOP:2026-09-30T09:30:00,START:2026-10-31T09:00:00,STOP:2026-10-31T09:30:00,START:2026-11-30T09:00:00,STOP:2026-11-30T09:30:00,START:2026-12-31T09:00:00,STOP:2026-12-31T09:30:00,START:2027-01-31T09:00:00,STOP:2027-01-31T09:30:00,START:2027-02-28T09:00:00,STOP:2027-02-28T09:30:00,START:2027-03-31T09:00:00,STOP:2027-03-31T09:30:00,START:2027-04-30T09:00:00,STOP:2027-04-30T09:30:00,START:2027-05-31T09:00:00,STOP:2027-05-31T09:30:00,START:2027-06-30T09:00:00,STOP:2027-06-30T09:30:00,START:2027-07-31T09:00:00,STOP:2027-07-31T09:30:00,START:2027-08-31T09:00:00,STOP:2027-08-31T09:30:00,START:2027-09-30T09:00:00,STOP:2027-09-30T09:30:00,START:2027-10-31T09:00:00,STOP:2027-10-31T09:30:00,START:2027-11-30T09:00:00,STOP:2027-11-30T09:30:00,START:2027-12-31T09:00:00,STOP:2027-12-31T09:30:00,START:2028-01-31T09:00:00,STOP:2028-01-31T09:30:00,START:2028-02-29T09:00:00,STOP:2028-02-29T09:30:00,START:2028-03-31T09:00:00,STOP:2028-03-31T09:30:00,START:2028-04-30T09:00:00,STOP:2028-04-30T09:30:00,START:2028-05-31T09:00:00,STOP:2028-05-31T09:30:00,START:2028-06-30T09:00:00,STOP:2028-06-30T09:30:00,START:2028-07-31T09:00:00,STOP:2028-07-31T09:30:00,START:2028-08-31T09:00:00,STOP:2028-08-31T09:30:00,START:2028-09-30T09:00:00,STOP:2028-09-30T09:30:00,START:2028-10-31T09:00:00,STOP:2028-10-31T09:30:00,START:2028-11-30T09:00:00,STOP:2028-11-30T09:30:00,START:2028-12-31T09:00:00,STOP:2028-12-31T09:30:00,START:2029-01-31T09:00:00,STOP:2029-01-31T09:30:00,START:2029-02-28T09:00:00,STOP:2029-02-28T09:30:00,START:2029-03-31T09:00:00,STOP:2029-03-31T09:30:00,START:2029-04-30T09:00:00,STOP:2029-04-30T09:30:00,START:2029-05-31T09:00:00,STOP:2029-05-31T09:30:00,START:2029-06-30T09:00:00,STOP:2029-06-30T09:30:00,START:2029-07-31T09:00:00,STOP:2029-07-31T09:30:00,START:2029-08-31T09:00:00,STOP:2029-08-31T09:30:00,START:2029-09-30T09:00:00,STOP:2029-09-30T09:30:00,START:2029-10-31T09:00:00,STOP:2029-10-31T09:30:00,START:2029-11-30T09:00:00,STOP:2029-11-30T09:30:00,START:2029-12-31T09:00:00,STOP:2029-12-31T09:30:00,START:2030-01-31T09:00:00,STOP:2030-01-31T09:30:00,START:2030-02-28T09:00:00,STOP:2030-02-28T09:30:00,START:2030-03-31T09:00:00,STOP:2030-03-31T09:30:00,START:2030-04-30T09:00:00,STOP:2030-04-30T09:30:00,START:2030-05-31T09:00:00,STOP:2030-05-31T09:30:00,START:2030-06-30T09:00:00,STOP:2030-06-30T09:30:00,START:2030-07-31T09:00:00,STOP:2030-07-31T09:30:00,START:2030-08-31T09:00:00,STOP:2030-08-31T09:30:00,START:2030-09-30T09:00:00,STOP:2030-09-30T09:30:00,START:2030-10-31T09:00:00,STOP:2030-10-31T09:30:00,START:2030-11-30T09:00:00,STOP:2030-11-30T09:30:00,START:2030-12-31T09:00:00,STOP:2030-12-31T09:30:00,START:2031-01-31T09:00:00,STOP:2031-01-31T09:30:00,START:2031-02-28T09:00:00,STOP:2031-02-28T09:30:00,START:2031-03-31T09:00:00,STOP:2031-03-31T09:30:00,START:2031-04-30T09:00:00,STOP:2031-04-30T09:30:00,START:2031-05-31T09:00:00,STOP:2031-05-31T09:30:00,START:2031-06-30T09:00:00,STOP:2031-06-30T09:30:00,START:2031-07-31T09:00:00,STOP:2031-07-31T09:30:00,START:2031-08-31T09:00:00,STOP:2031-08-31T09:30:00,START:2031-09-30T09:00:00,STOP:2031-09-30T09:30:00,START:2031-10-31T09:00:00,STOP:2031-10-31T09:30:00,START:2031-11-30T09:00:00,STOP:2031-11-30T09:30:00,START:2031-12-31T09:00:00,STOP:2031-12-31T09:30:00,START:2032-01-31T09:00:00,STOP:2032-01-31T09:30:00,START:2032-02-29T09:00:00,STOP:2032-02-29T09:30:00,START:2032-03-31T09:00:00,STOP:2032-03-31T09:30:00,START:2032-04-30T09:00:00,STOP:2032-04-30T09:30:00,START:2032-05-31T09:00:00,STOP:2032-05-31T09:30:00,START:2032-06-30T09:00:00,STOP:2032-06-30T09:30:00,START:2032-07-31T09:00:00,STOP:2032-07-31T09:30:00,START:2032-08-31T09:00:00,STOP:2032-08-31T09:30:00,START:2032-09-30T09:00:00,STOP:2032-09-30T09:30:00,START:2032-10-31T09:00:00,STOP:2032-10-31T09:30:00,START:2032-11-30T09:00:00,STOP:2032-11-30T09:30:00,START:2032-12-31T09:00:00,STOP:2032-12-31T09:30:00,START:2033-01-31T09:00:00,STOP:2033-01-31T09:30:00,START:2033-02-28T09:00:00,STOP:2033-02-28T09:30:00,START:2033-03-31T09:00:00,STOP:2033-03-31T09:30:00,START:2033-04-30T09:00:00,STOP:2033-04-30T09:30:00,START:2033-05-31T09:00:00,STOP:2033-05-31T09:30:00,START:2033-06-30T09:00:00,STOP:2033-06-30T09:30:00,START:2033-07-31T09:00:00,STOP:2033-07-31T09:30:00,START:2033-08-31T09:00:00,STOP:2033-08-31T09:30:00,START:2033-09-30T09:00:00,STOP:2033-09-30T09:30:00,START:2033-10-31T09:00:00,STOP:2033-10-31T09:30:00,START:2033-11-30T09:00:00,STOP:2033-11-30T09:30:00,START:2033-12-31T09:00:00,STOP:2033-12-31T09:30:00,START:2034-01-31T09:00:00,STOP:2034-01-31T09:30:00,START:2034-02-28T09:00:00,STOP:2034-02-28T09:30:00,START:2034-03-31T09:00:00,STOP:2034-03-31T09:30:00,START:2034-04-30T09:00:00,STOP:2034-04-30T09:30:00,START:2034-05-31T09:00:00,STOP:2034-05-31T09:30:00,START:2034-06-30T09:00:00,STOP:2034-06-30T09:30:00,START:2034-07-31T09:00:00,STOP:2034-07-31T09:30:00,START:2034-08-31T09:00:00,STOP:2034-08-31T09:30:00

If you're okay with February 28th for leap years a schedule can be simplified to 12 entries with * in the year.

If all of the above is not an option create a separate Business Service which runs on a first day of a month, calculates last day of a month and sets a correct schedule for BO.

Eduard Lebedyuk · Sep 13, 2024 go to post

Great Article, Vic!

Please consider embedding URLs.

Also there are Object and SQL interfaces to journal records, which sometimes might be useful.

Eduard Lebedyuk · Sep 9, 2024 go to post

$zf should work:

w$zf(-100,"/SHELL","eventcreate","/ID",1,"/L","APPLICATION","/T","INFORMATION","/SO","MYEVENTSOURCE","/D","My first log")
Eduard Lebedyuk · Sep 6, 2024 go to post

While I agree that ideally you'd run two IRIS nodes in two geographically close but fully separate datacenters, running IRIS in a mirror with both servers in the same datacenter still provides protection from:

  • Server hardware failure
  • OS-level failure
  • IRIS failure

In addition to that datacenters often allow users to specify placement strategy. Select spread placement strategy to avoid hosting both servers on the same underlying hardware if possible.

So mirroring in this scenario still provides a lot of advantages.

Eduard Lebedyuk · Aug 30, 2024 go to post

Sure, you have two approaches:

  1. Define a message class with XMLPROJECTION=NONE for the properties you want to skip
  2. Redefine %ShowContents method for your message class to skip some of the properties (check Ens.Util.MessageBodyMethods for a sample implementation).
Eduard Lebedyuk · Aug 21, 2024 go to post

Can you copy FOP to two places (or each job at a start copies FOP to it's own dir) and call it in parallel after that. Would that work?

Eduard Lebedyuk · Aug 9, 2024 go to post

I agree that

[ CodeMode = objectgenerator ]

is certainly outside of this code golf conditions (method signature should not be changed), but it's still a creative example.