Hi Keshav,

%ID is a psuedo field that will always reference the RowID, by default this is named as ID, but could also be ID1, ID2 .. IDn

More about it here...

https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GSQL_tables#GSQL_tables_idfield

Not sure about your error, would need to see your full code.

If you are not naming properties as ID, then just use ID.

Sean.

A couple of thoughts, as you have a backslash in the path, the new file path is going to be a concat of ..Adapter.%serverPath and NewFilePath, I would log this and not just the fullPath so you can be sure what directory you are trying to write to...

$$$TRACE("Path for archive: "_..Adapter.%serverPath_NewFilePath)

Also, might just be worth checking that you are connected when you do the rename

$$$TRACE("Connected: ",..Adapter.Connected)

Hi Stephen,

Interesting question.

The routing engine gets passed a context object that contains a Document object. This is the request object, so in your example you are directly accessing your Ens.AlertRequest with Document.AlertText.

I did a full dump of the context object so you can see what properties are available...

{
   "ActionTargets":"Foo.Productions.TEST2RoutingRule",
   "Adapter":"",
   "AlertGroups":"",
   "AlertOnBadMessage":null,
   "AlertOnError":false,
   "AlertRetryGracePeriod":0,
   "BadMessageHandler":"",
   "BusinessPartner":"",
   "BusinessRuleName":"Foo.Productions.TEST2RoutingRule",
   "Document":{
      "AlertDestination":"The alert destination",
      "AlertText":"The alert text",
      "AlertTime":"2018-10-29 15:29:18.517",
      "SessionId":null,
      "SourceConfigName":"souce"
   },
   "FailureTimeout":15,
   "ForceSyncSend":null,
   "InactivityTimeout":0,
   "MsgClass":"Ens.AlertRequest",
   "QueueCountAlert":0,
   "QueueWaitAlert":0,
   "ReplyCodeActions":"",
   "ResponseFrom":"",
   "ResponseTargetConfigNames":"",
   "ResponseTimeout":-1,
   "Retry":false,
   "RetryInterval":5,
   "Source":"EnsLib.Testing.Process",
   "SuspendMessage":false,
   "ThrottleDelay":0,
   "Validation":"",
   "aRespFrom":{

   }
}

There doesn't look to be any access to the Header object on the context object.

So I did a dump of the stack to see if its visible outside of the context object, but it looks like the routing rules are scoped off to just the arguments it takes...

{
   "Objects":{
      "pContext":{
         "Properties":{
            "ActionTargets":"Foo.Productions.TEST2RoutingRule",
            "Adapter":"",
            "AlertGroups":"",
            "AlertOnBadMessage":null,
            "AlertOnError":false,
            "AlertRetryGracePeriod":0,
            "BadMessageHandler":"",
            "BusinessPartner":"",
            "BusinessRuleName":"Foo.Productions.TEST2RoutingRule",
            "Document":{
               "AlertDestination":"3",
               "AlertText":"2",
               "AlertTime":"2018-10-29 15:36:24.273",
               "SessionId":null,
               "SourceConfigName":"1"
            },
            "FailureTimeout":15,
            "ForceSyncSend":null,
            "InactivityTimeout":0,
            "MsgClass":"Ens.AlertRequest",
            "QueueCountAlert":0,
            "QueueWaitAlert":0,
            "ReplyCodeActions":"",
            "ResponseFrom":"",
            "ResponseTargetConfigNames":"",
            "ResponseTimeout":-1,
            "Retry":false,
            "RetryInterval":5,
            "Source":"EnsLib.Testing.Process",
            "SuspendMessage":false,
            "ThrottleDelay":0,
            "Validation":"",
            "aRespFrom":{

            }
         },
         "Reference":"3@EnsLib.MsgRouter.RoutingEngine"
      }
   },
   "Primatives":{
      "pEffectiveBegin":"",
      "pEffectiveEnd":"",
      "pPassed":"1",
      "pReason":"",
      "pReturnValue":"",
      "pRuleSet":"",
      "tSC":"1"
   },
   "Stack":{
      "1":"zStart+62^Ens.Job.1 +2 : Set tInstance.%QuitTask=0, tSC=tInstance.OnTask()  Quit:('tSC)||tInstance.%QuitTask",
      "2":"zOnTask+42^Ens.Host.1 +1 : Set tSC=..MessageHeaderHandler(tRequestHeader,.tResponseHeader)",
      "3":"zMessageHeaderHandler+42^Ens.Actor.1 +1 : Set tSC=tBP.MessageHeaderHandler(pRequestHeader,.pResponseHeader,.tHandledError) ; Sets SessionId, we clear it",
      "4":"zMessageHeaderHandler+18^Ens.BusinessProcess.1 +1 : Set tSC=..OnRequest(..%request,.tResponse)",
      "5":"zOnRequest+21^EnsLib.MsgRouter.RoutingEngine.1 +1 : Quit ..doOneAction(request,\"rule:\"_tRuleName,,,.response) }",
      "6":"zdoOneAction+8^EnsLib.MsgRouter.RoutingEngine.1 +1 : Set tSC=##class(Ens.Rule.RuleDefinition).EvaluateRulesEx(tOneTarg, ..%SessionId, $this, $classname(), .tActionList) Quit:('tSC)",
      "7":"zEvaluateRulesEx+3^Ens.Rule.RuleDefinition.1 +1 : Quit ##class(Ens.Rule.Definition).EvaluateRules(pRuleName,pSessionId,pContext,pActivityName,.pReturnValue,.pReason)",
      "8":"zEvaluateRules+12^Ens.Rule.Definition.1 +1 : set tSC=$classmethod(tClassName,\"evaluateRuleDefinition\",pContext,.tRuleSet,.tEffectiveBegin,.tEffectiveEnd,.pReturnValue,.pReason)",
   }
}

So I think the short answer is going to be not directly.

I can access the request ID in the code behind, using the %Id() method, but the rule validator doesn't seem to like referencing it.

I had a quick hack and managed to find one way by using a custom function, its a hack but something like this, but maybe using dynamic SQL to access the value...

 Class Foo.FSET Extends Ens.Rule.FunctionSet
{

ClassMethod HeaderPropertyEquals(obj, name, value)
{
set bid=obj.%Id()
&sql(select id into :id from Ens.MessageHeader where MessageBodyId=:bid)
set mh=##class(Ens.MessageHeader).%OpenId(id)
quit $property(mh,name)=value
}

}

You can then call this function in the rule set as a condition...

HeaderPropertyEquals(Document,"SourceConfigName","Foo Service")

Can't say I have ever wanted to access the Header object, but interesting to hack around the code behind to see whats going on.

Sean

Hi Sabit,

This code looks familiar, a 5 minute hack from another answer I made blush

The problem is probably with %File, try replacing the file output with the following code...


set file=##class(%FileBinaryStream).%New()
set file.Filename=filename
quit file.OutputToDevice()

If you are still getting a file not found then the file name is probably wrong / corrupt, try logging the file name and opening the file from the command line to be sure.

Sean.

Hi Arun,

Looks like the test form doesn't support the payload property type.

You will have more luck creating the request object from the command line and then calling the test service, something along these lines...

set sc=##class(EnsLib.Testing.Service).SendTestRequest("Put Target Name Here",request,.response,.sessionId,1)

You will need to create an instance of a stream object and assign it to the Stream property of the request object.

If you don't have a WSDL then you could try contacting the TRUD for ITK documentation...

https://isd.digital.nhs.uk/trud3/user/guest/group/0/pack/30

There is one tip that I use when I don't have a WSDL. I take an example XML message and create an XSD for it, there are online generators that can help you do this, such as this one here...

https://www.freeformatter.com/xsd-generator.html

You can then paste the XSD into the XML Schema Wizard found in Studio > Tools > Add-Ins > Add-Ins, this will generate a class that you can use as your request object.

Sean.

Hi Tiago,

The ? pattern match is native to COS and has been in the product since day 1. You will also find it in GT.m and a few other fringe M systems, but probably nowhere else.

The $match feature was introduced back in 2012. It uses an external Regex library called ICU.

Both are highly optimised for general use cases (millions of operations per second).

The ? pattern matching syntax is less expressive than Regex, but this also makes it easy to learn and remember.

Regex would be the better choice for more complex pattern matching requirements.

Sean.

yes typing too fast, should have pointed out the -1 was for reference to trim at the other end

you can get a full list of these type of functions here...

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

and a bit more information specifically on working with strings here...

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

Hi Oliver,

The Transform is a generated method, it doesn't make sense to override it and call its base implementation, hence the ##super() error.

If you want the Transform status code then just call the generated Transform on your transform class from the command line, e.g.

write ##class(My.Transform).Transform(in,.out)

But you are not going to get anything meaningful from this unless you actual capture and return the error, so you might want some code inside your transform like this...

Set tSCTrans=target.%Save() if ('tSCTrans) goto Exit

But this doesn't feel right.

I would have a custom process and do it this way...

set sc=##class(My.Transform).Transform(in,.out)
if $$$ISERR(sc) quit sc
set sc=out.%Save()
quit sc

You will quit the status code up to the ensemble director that will then handle the error logging for you.

And I bet you a pint that one of your X12 fields is longer than 50 characters and you are getting a MAXLEN error returned from the save method.

Sean.

Hi Richard,

You can create and delete accounts via the %SYS namespace using the following class methods...

https://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=%25SYS&CLASSNAME=Security.Users

It sounds like you might want to take a look at delegated authentication as well...

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

If this is just for a CSP application then you can  detect the user logging out or a session timeout using the %CSP.SessionEvents class.

Sean.

Hi Mauri,

The short answer is going to be not via the reply code actions.

You can customise an operation to send a message back around (..SendRequestAsync for instance) which would put it to the back of the queue.

You don't mention the type of message or error that would require this type of behaviour. It does seem a little odd since a message that fails either fails because of a network / end point down problem, or that the message data has failed validation. In the first instance you would most likely want to just keep retrying the same message with a failure timeout of -1, and in the second instance, a message that fails validation would surely keep failing validation until you manually fixed and resent the problem by hand.

Sean.

Hi Sansa,

If you are getting back an empty string (I suspect not null) then you should be able to use the GetError method to investigate the problem. This is from the class method comments...

If the function fails, it returns "". 
The actual session error status can be returned by a call to the GetError method.

Examples:

s Status=##Class(%SYS.LDAP).SearchExts(LD,BaseDN,$$$LDAPSCOPESUBTREE,Filter,Attributes,0,"","",2,ServerTimeout,.SearchResult)
i Status'=$$$LDAPSUCCESS q Status
s Entry=##Class(%SYS.LDAP).FirstEntry(LD,SearchResult)
s Status=##Class(%SYS.LDAP).GetError(LD)
i Status'=$$$LDAPSUCCESS q Status
s ValueList=##Class(%SYS.LDAP).GetValues(LD,Entry,"sAMAccountname") i ValueList="" q ##Class(%SYS.LDAP).GetError(LD)
w !,"sAMAccountname="_$li(ValueList,1)

It's certainly worth looking at the class documentation for this...

https://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls

From experience LDAP can be tricky to get working, and even harder to help diagnose without having all the information at hand. It's analogous to posting the question, my car won't start, whats the problem. It can be 101 things, so you will need to narrow down the error and provide as much information as you can if you are still stuck.

Sean.