go to post Robert Barbiaux · Feb 22 You can query the message body class table with a left join to Ens.MessageHeader to get the orphaned message body identifiers : select %NOLOCK bod.Id from MySample.BodyTable bod left join Ens.MessageHeader hdr on bod.Id=hdr.MessageBodyId and hdr.MessageBodyClassName='MySample.BodyTable' where hdr.MessageBodyId is NULL
go to post Robert Barbiaux · Feb 6 An approach using an operation that, on message : enqueue incoming message in a queue named after patient identifier dequeue and process message from all existing patient queues If PoolSize = 1, it will process 1 message at a time. If PoolSize > 1, multiple jobs will process incoming messages from other operations in sequence for each patient Class dc.interop.PatientMessageOperation Extends Ens.BusinessOperation { Parameter SETTINGS = "DeQueueTimeout,MinProcessTime,MaxProcessTime"; Property DeQueueTimeout As %Integer [ InitialExpression = 1 ]; Property MinProcessTime As %Integer [ InitialExpression = 1 ]; Property MaxProcessTime As %Integer [ InitialExpression = 5 ]; Method HandlePatientMessage(request As test.src.ut.ks.lib.interop.PatientMessage, Output response As %Persistent) As %Status { #Dim sc as %Status #Dim ex as %Exception.AbstractException s sc = $$$OK try { $$$TOE(sc,..EnqueueRequest(request)) $$$TOE(sc,..ProcessPatientMessages()) } catch (ex) { s sc = ex.AsStatus() } return sc } Method EnqueueRequest(request As PatientMessage) As %Status { #Dim sc as %Status #Dim ex as %Exception.AbstractException #Dim queue as Ens.Queue #Dim queueName as %String #Dim s as EnsLib.File.InboundAdapter s sc = $$$OK try { s queueName = ..%ConfigName_".patient."_request.PatientId $$$TOE(sc,##class(Ens.Queue).Create(queueName)) s ..%RequestHeader.TargetQueueName = queueName $$$TOE(sc,##class(Ens.Queue).EnQueue(..%RequestHeader)) s $$$EnsRuntimeAppData(..%ConfigName,"queues",queueName) = 1 } catch (ex) { s sc = ex.AsStatus() } return sc } Method ProcessPatientMessages() As %Status { #Dim sc as %Status #Dim ex as %Exception.AbstractException s sc = $$$OK try { s queueName = "" s queueName = $order($$$EnsRuntimeAppData(..%ConfigName,"queues",queueName)) while queueName '= "" { $$$TOE(sc,..ProcessQueuedMessages(queueName)) s queueName = $order($$$EnsRuntimeAppData(..%ConfigName,"queues",queueName)) } } catch (ex) { s sc = ex.AsStatus() } return sc } Method ProcessQueuedMessages(queueName As %String) As %Status { #Dim sc as %Status #Dim ex as %Exception.AbstractException #Dim header as Ens.MessageHeader #Dim timedOut as %Boolean s sc = $$$OK try { $$$TOE(sc,##class(Ens.Queue).DeQueue(queueName,.header,..DeQueueTimeout,.timedOut,1)) while 'timedOut && $isobject(header) { $$$TOE(sc,..ProcessHeader(header)) $$$TOE(sc,##class(Ens.Queue).DeQueue(queueName,.header,..DeQueueTimeout,.timedOut,1)) } } catch (ex) { s sc = ex.AsStatus() } return sc } Method ProcessHeader(header As Ens.MessageHeader) As %Status { #Dim sc as %Status #Dim ex as %Exception.AbstractException #Dim msg as PatientMessage s sc = $$$OK try { s msg = $classmethod(header.MessageBodyClassName,"%OpenId",header.MessageBodyId) $$$TOE(sc,..ProcessMessage(msg)) } catch (ex) { s sc = ex.AsStatus() } return sc } Method ProcessMessage(msg As PatientMessage) As %Status { #Dim sc as %Status #Dim ex as %Exception.AbstractException #Dim processTime as %Integer s sc = $$$OK try { $$$TRACE("job "_$job_" is processing message for patient "_msg.PatientId_" seq "_msg.Seq) s processTime = $random(..MaxProcessTime-..MinProcessTime)+..MinProcessTime hang processTime $$$TRACE("job "_$job_" processed message for patient "_msg.PatientId_" seq "_msg.Seq_" in "_processTime_" seconds") } catch (ex) { s sc = ex.AsStatus() } return sc } Method OnTearDown() As %Status { #Dim sc as %Status #Dim ex as %Exception.AbstractException s sc = $$$OK try { s queueName = "" s queueName = $order($$$EnsRuntimeAppData(..%ConfigName,"queues",queueName)) while queueName '= "" { $$$TOE(sc,##class(Ens.Queue).Delete(queueName,"*")) s queueName = $order($$$EnsRuntimeAppData(..%ConfigName,"queues",queueName)) } } catch (ex) { s sc = ex.AsStatus() } return sc } XData MessageMap { <MessageMap> <MapItem MessageType="ut.ks.lib.interop.PatientMessage"> <Method>HandlePatientMessage</Method> </MapItem> </MessageMap> } } The message class Class ut.ks.lib.interop.PatientMessage Extends Ens.MessageBody { Property PatientId As %Integer; Property Seq As %Integer; Property Payload As %String; Storage Default { <Data name="PatientMessageDefaultData"> <Subscript>"PatientMessage"</Subscript> <Value name="1"> <Value>PatientId</Value> </Value> <Value name="2"> <Value>Seq</Value> </Value> <Value name="3"> <Value>Payload</Value> </Value> </Data> <DefaultData>PatientMessageDefaultData</DefaultData> <Type>%Storage.Persistent</Type> } } Sample test method ClassMethod SendMessages(patientCount As %Integer = 100, messageCount = 1000) As %Status { #Dim sc as %Status #Dim ex as %Exception.AbstractException s sc = $$$OK try { for i = 1:1:messageCount { s msg = ##class(PatientMessage).%New() s msg.PatientId = $random(patientCount)+1 s msg.Seq = $increment(seq(msg.PatientId)) s msg.Payload = "message "_msg.Seq_" for "_msg.PatientId $$$TOE(sc,##class(EnsLib.Testing.Service).SendTestRequest("PatientOperation",msg)) } } catch (ex) { s sc = ex.AsStatus() } return sc }
go to post Robert Barbiaux · Feb 5 Another approach would be to write a custom router class extending EnsLib.MsgRouter.RoutingEngine, overriding OnRequest() method. In OnRequest(), before calling ##super(), assign the JSON %DynamicAbstractObject build from the input request stream to a property. Use the property in the router rules.
go to post Robert Barbiaux · Jan 24 You can override the Page() method of %CSP.Page and redirect output to a stream, process the resulting stream and write it to the original device the page is using to send data back to client.Here is a quick and dirty example, using IO-Redirect package available on OpenExchange.The redirecting page : Class test.src.RedirectedPage Extends %CSP.Page { ClassMethod Page(skipHeader As %Boolean = 1) As %Status [ ServerOnly = 1 ] { #dim sc as %Status #dim ex as %Exception.AbstractException #dim pageStream,processedPageStream As %Stream.Object #dim len as %Integer #dim buffer as %String s sc = $$$OK try { Set pageStream = ##class(%Stream.GlobalCharacter).%New() Do ##class(IORedirect.Redirect).ToStream(pageStream) $$$TOE(sc,##super(skipHeader)) Do ##class(IORedirect.Redirect).RestoreIO() Set pageStream = ##class(IORedirect.Redirect).Get() $$$TOE(sc,..ProcessPageStream(pageStream,.processedPageStream)) while 'processedPageStream.AtEnd { s len = 32768 s buffer = processedPageStream.Read(.len,.sc) $$$TOE(sc,sc) write buffer } } catch (ex) { s sc = ex.AsStatus() } return sc } ClassMethod ProcessPageStream(pageStream As %Stream.Object, Output processedPageStream As %Stream.Object) As %Status { #dim sc as %Status #dim ex as %Exception.AbstractException s sc = $$$OK try { s processedPageStream = ##class(%Stream.TmpCharacter).%New() $$$TOE(sc,processedPageStream.CopyFrom(pageStream)) d processedPageStream.Write("<div><span>original page had "_pageStream.Size_" bytes </span></div>") } catch (ex) { s sc = ex.AsStatus() } return sc } } The original page : Class test.src.OriginalPage Extends test.src.RedirectedPage { ClassMethod OnPage() As %Status [ ServerOnly = 1 ] { &html< <div> <span>Hello, world, again</span> </div> > return $$$OK }
go to post Robert Barbiaux · Jan 16 To see the content, the CSP Gateway has an HTTP trace facility, see documentation. To access the response just before it is sent back to client, override methods of %CSP.Page
go to post Robert Barbiaux · Dec 12, 2024 You can use the auxiliary property (aux attribute) of the <subtransform> DTL element to pass the index of the group to process to the subtransform, passing the entire source message as source object.
go to post Robert Barbiaux · Nov 10, 2024 EnsLib.EDI.XML.Document implements Ens.VDoc.Interface, and exposes GetValueAt(propertyPath) method.This method accepts XPath-like property paths such as "/foo/bar" or "/foo/bar/@attr".This also means you can use the curly brace syntax in business rules (classes extending Ens.Rule.Definition).For example, if the context class has a "xmlDocument" property, this expressionxmlDocument.{/foo/bar}will be compiled into xmlDocument.GetValueAt("/foo/bar")
go to post Robert Barbiaux · Sep 25, 2024 For example (in open exchange package ks-iris-lib) : /// list of data types exposing %GetSerial and %SetSerial Class ks.lib.collections.ListOfDataTypes Extends %Library.ListOfDataTypes { /// serialize object Method %GetSerial(force As %Integer = 0) As %String { return ##super(force) } /// deserialize object Method %SetSerial(serialized As %String) As %Status { return ##super(.serialized) } Storage Custom { <Type>%Library.CompleteCustomStorage</Type> } }
go to post Robert Barbiaux · Sep 23, 2024 There is a limit to string length, as explained in the documentation.To convert a binary stream to a base64 encoded character stream, you have to call the function in a loop using a buffer. Due the way the function works, the buffer length must be a multiple of 57.Here a sample implementation, from ks.lib.stream.Utils class in the ks-iris-lib package available on Open Exchange. /// encode stream data into encoded using bufferSize (due the implementation of $system.Encryption.Base64Encode, this must be a multiple of 57) ClassMethod Base64Encode(stream As %Stream.Object, Output encoded As %Stream.Object, bufferSize As %Integer = 5700) As %Status { #Dim sc as %Status #Dim ex as %Exception.AbstractException #Dim len As %Integer s sc = $$$OK try { throw:((bufferSize#57)'=0) ##class(%Exception.General).%New("buffer size must be a multiple of 57") $$$TOE(sc,stream.Rewind()) s:'$d(encoded) encoded = ##class(%Stream.TmpCharacter).%New() s len=bufferSize while 'stream.AtEnd { $$$TOE(sc,encoded.Write($system.Encryption.Base64Encode(stream.Read(.len),1))) s len = bufferSize } } catch (ex) { s sc = ex.AsStatus() } return sc }
go to post Robert Barbiaux · Aug 7, 2024 Using the second approach : edited down to 39 characters ClassMethod ascii() { f i=32:1:126 w:$t(ascii+1)'[$c(i) *i }
go to post Robert Barbiaux · Aug 7, 2024 ah, yes, thanks, comments are indeed part of the source code 😁
go to post Robert Barbiaux · Aug 7, 2024 Yes Enrico, it is my understanding exactly : if your program code (i.e. the lines in the routine generated by the compiler) contains all printable ASCII characters, it should output an empty string. Of course it sounds silly to devise a very clever way to output an empty string, but after all, golf - from a practical point of view - is a silly sport isn't it ? 🤣
go to post Robert Barbiaux · Aug 7, 2024 Alas, I'm well behind : 54 characters (according to %Dictionary.MethodDefinition Implementation.Size).Please, enlighten me 😅 ClassMethod ascii() [ CodeMode = objectgenerator ] { f i=0,83,0,33,29,2,3:1:94,2 d %code.Write($c(i+32)) }
go to post Robert Barbiaux · Jun 12, 2024 Hadn’t used it since Ensemble 2009 ;-), to provide custom html output displayed by the management portal when viewing a message of a class extending Ens.MessageBody, you can override the %GetContentType() and %ShowContents() methods of Ens.Util.MessageBodyMethods in your message class.Here is a small example : Class dc.sample.msg.Message Extends Ens.MessageBody { Property Name As %String [ InitialExpression = "you" ]; // returns MIME content type Method %GetContentType() As %String { return "text/html" } // output content Method %ShowContents() { &html<<p>Hello, #(..Name)#</p>> } Storage Default { <Data name="MessageDefaultData"> <Subscript>"Message"</Subscript> <Value name="1"> <Value>Name</Value> </Value> </Data> <DefaultData>MessageDefaultData</DefaultData> <Type>%Storage.Persistent</Type> } } In the management portal message viewer, the message gets displayed as :
go to post Robert Barbiaux · Jun 5, 2024 What is the class of config item ‘TEST_ROUTER’ ? Maybe not EnsLib.HL7.MsgRouter.RoutingEngine, which exposes the HL7 property.
go to post Robert Barbiaux · May 27, 2024 The message is not valid as per HL7 v2.5 section 4.4.6. The second ORC...TQ1...OBR is invalid, it should be ORC...OBR...TQ1 : in the ORDER_PRIOR group, ORC is optionnal and must be followed by OBR followed by optional NTE and then TQ1 group. Keep in mind : IRIS HL7 schemas are based on formal grammars published by HL7.org.
go to post Robert Barbiaux · May 10, 2024 The curly brace syntax "source.{PID:12}" is not supported in code action as the block is included as is in the class code generated at compile time. Use GetValueAt() to achieve the same effect : Set country = source.GetValueAt("PID:12")
go to post Robert Barbiaux · Apr 11, 2024 You can use $classmethod to invoke any class method, including %New, e.g. set cls = "MyPackage.MyClass" set obj = $classmethod(cls,"%New")
go to post Robert Barbiaux · Apr 10, 2024 When dealing with stream input, it’s good to keep in mind that strings are limited in length, as explained in the documentaton.
go to post Robert Barbiaux · Mar 14, 2024 Indeed f j=$i(r)... would get a !! mark if it was a chess move 😉Congratulations !