Brendan Batchelder · Aug 24, 2016 go to post

Hi Duncan,

The standard lookup tables are the closest thing to what you're looking for but as you noted, they don't support reverse lookups.  I think you will need something custom for this.  The easiest solution is probably to implement a reverse lookup for existing lookup tables.  If you implement a new classmethod to do this in a class that extends Ens.Rule.FunctionSet, then that classmethod will be available to DTLs.

-Brendan

Brendan Batchelder · Aug 24, 2016 go to post

Hi Nupur,

I think solving this problem will take some investigation.  I recommend opening a WRC problem.

-Brendan

Brendan Batchelder · Aug 17, 2016 go to post

Hi Raghu,

Stream objects have an Attributes collection which holds the incoming HTTP headers.  I believe you should be able to check the incoming Content Type with pInput.GetAttribute("Content-Type") but I haven't confirmed this.

-Brendan

Brendan Batchelder · Aug 11, 2016 go to post

The testing form isn't smart enough to handle array or list properties.  The easiest way I've found to test these is to create a  file business service and add code to populate the array with some hard-coded values.  Then add that service to your production, set the file path, and copy any random file into the file path directory to trigger the service.  Here's an example file service to do this:

Class Community.TestArray Extends Ens.BusinessService
{
  Parameter ADAPTER = "EnsLib.File.InboundAdapter";
  Method OnProcessInput(pInput As %Stream.Object, Output pOutput As %RegisteredObject) As %Status
  {
    set tTestMsg = ##class(MyApp.MyRequest).%New()
    do tTestMsg.idToTokenArray.Insert("One")
    do tTestMsg.idToTokenArray.Insert("Two")
    do tTestMsg.idToTokenArray.Insert("Three")
    quit ..SendRequestAsync("OperationToTest",tTestMsg)
  }
}
 

Brendan Batchelder · Jul 25, 2016 go to post

Make sure the 'Enable Standard Requests' setting on the REST business service is checked, Port is blank, and Pool Size is set to 0.  This will ensure the business service only listens for requests through the CSP Gateway and not through its own port.  Be sure to construct the URL for accessing the REST service as described in the documentation here, under "Defining the URL Using UrlMap and EnsServicePrefix":

http://docs.intersystems.com/ens20161/csp/docbook/DocBook.UI.Page.cls?K…

Brendan Batchelder · Jul 20, 2016 go to post

+1

This is what the Ensemble message viewer does when the search criteria includes a date/time range.

Brendan Batchelder · Jul 19, 2016 go to post

I see $$$TRACE statements in your code, which leads me to believe you are using Ensemble.

What you're trying to do is much easier with EnsLib.EDI.XML.Document, and there are adapters supporting the use of this message class.

Here's an example:

ENSEMBLE>set msg = ##class(EnsLib.EDI.XML.Document).ImportFromString(tData)
ENSEMBLE>w msg.GetValueAt("/")
<HHSOS><DIAGNOSES><DIAGNOSIS_DATA><DIAGNOSIS_DATA_GUID>3762875</DIAGNOSIS_DATA_GUID><DIAGNOSIS_DATA_GUID>37628752</DIAGNOSIS_DATA_GUID></DIAGNOSIS_DATA><DIAGNOSIS_DATA/><DIAGNOSIS_DATA/><DIAGNOSIS_DATA_GUID>37628753</DIAGNOSIS_DATA_GUID></DIAGNOSES></HHSOS>
ENSEMBLE>w msg.GetValueAt("/HHSOS/DIAGNOSES")
<DIAGNOSIS_DATA><DIAGNOSIS_DATA_GUID>3762875</DIAGNOSIS_DATA_GUID><DIAGNOSIS_DATA_GUID>37628752</DIAGNOSIS_DATA_GUID></DIAGNOSIS_DATA><DIAGNOSIS_DATA/><DIAGNOSIS_DATA/><DIAGNOSIS_DATA_GUID>37628753</DIAGNOSIS_DATA_GUID>
ENSEMBLE>w msg.GetValueAt("/HHSOS/DIAGNOSES/DIAGNOSIS_DATA[*]")
3
ENSEMBLE>w msg.GetValueAt("/HHSOS/DIAGNOSES/DIAGNOSIS_DATA[1]")
<DIAGNOSIS_DATA_GUID>3762875</DIAGNOSIS_DATA_GUID><DIAGNOSIS_DATA_GUID>37628752</DIAGNOSIS_DATA_GUID>
ENSEMBLE>w msg.GetValueAt("/HHSOS/DIAGNOSES/DIAGNOSIS_DATA[2]")
ENSEMBLE>w msg.GetValueAt("/HHSOS/DIAGNOSES/DIAGNOSIS_DATA[1]/DIAGNOSIS_DATA_GUID[*]")
2
ENSEMBLE>w msg.GetValueAt("/HHSOS/DIAGNOSES/DIAGNOSIS_DATA[1]/DIAGNOSIS_DATA_GUID[1]")
3762875
ENSEMBLE>w msg.GetValueAt("/HHSOS/DIAGNOSES/DIAGNOSIS_DATA[1]/DIAGNOSIS_DATA_GUID[2]")
37628752
 

These are called DOM-style paths as opposed to VDoc paths.  Here's the documentation:

http://docs.intersystems.com/ens20161/csp/docbook/DocBook.UI.Page.cls?K…;

Brendan Batchelder · Jul 19, 2016 go to post

Basic Class Queries are similar to stored procedures.  You can hard-code some query parameters while allowing others to be passed as arguments.  The query is pre-compiled in the code.  With a dynamic query, when you call the .Prepare method, this causes code to be generated at runtime to run the query.

Embedded SQL is also pre-compiled into the code.  One benefit basic class queries have over embedded SQL is that basic class queries can be exposed to SQL, allowing them to be called like stored procedures.

Brendan Batchelder · Jul 19, 2016 go to post

If you use Managed Alerts, there is a custom FunctionSet function IsRecentManagedAlert() which you can use to suppress repeated alerts.  Here's an excerpt from the documentation:

The rule can check whether the alert is a repeat occurrence of a previous alert that is represented by a currently open managed alert. To do this, the rule uses the Ens.Alerting.Rule.FunctionSet.IsRecentManagedAlert() function. The IsRecentManagedAlert() function tests if there is a recent open managed alert that is from the same component and has the same text as the specified alert. You can optionally specify that the function adds a reoccurs action to the existing managed alert.

http://docs.intersystems.com/ens20161/csp/docbook/DocBook.UI.Page.cls?K…

Brendan Batchelder · Jul 19, 2016 go to post

To do this in the message viewer, add an extended criteria to your search parameters.  Criterion Type = VDoc Property Path.  Class = EnsLib.HL7.Message.  DocType = Schema:Name of the type of documents you're searching for (e.g. 2.3.1:ADT_A01).  Property Path = the VDoc path of the field you're searching on (e.g. MSH:SendingApplication)

It should be noted that the message viewer will use SQL to get a list of messages based on the 'Basic Criteria' and then it loops over the results applying each of the 'Extended Criteria' as a filter.  This can be very slow.  If it takes more than 2 minutes, the message viewer will time out and either the Basic Criteria needs to be refined in order to return fewer results, or the search for messages must be done with code.

To do this with code, use embedded SQL to search on message headers based on basic criteria and then loop over the results checking VDoc paths.  Here's an example which looks for messages from a particular day that have a particular value in MSH:3, the Sending Application field:

&SQL(DECLARE C1 CURSOR FOR SELECT MessageBodyId INTO :id FROM Ens.MessageHeader WHERE MessageBodyClassName='EnsLib.HL7.Message' AND TimeCreated BETWEEN '2016-07-15' AND '2016-07-16')
&SQL(OPEN C1)
&SQL(FETCH C1)
while (SQLCODE = 0) {
   set msg = ##class(EnsLib.HL7.Message).%OpenId(id)
   if (msg.GetValueAt("MSH:SendingApplication") = "Ensemble") {
      //do something with this message
   }
   &SQL(FETCH C1)
}
&SQL(CLOSE C1)
Brendan Batchelder · Jul 14, 2016 go to post

%Ensemble("SessionId") does hold the session id, but by using it you're breaking the abstraction barrier.  There's no guarantee that variable name will continue to be used in future versions.  Why do you need the SessionId in a DTL?  There may be a better way to solve the problem you're trying to solve.

In later versions, individual components and their settings can be moved with the Deployment feature in Ensemble.  In your staging instance on the production configuration page, select the component you want to move, switch to the 'Actions' tab, and click the 'Export' button.  This will allow you to export the code associated with the component along with a .ptd file which contains all of the settings.  Then, in the production instance, from the main page of the management portal, go to Ensemble->Manage->Deployment Changes->Deploy and select the xml file you just exported.  This will allow you to import the component and all the settings into the production instance without changing anything else in the production class.

I saw one of these recently.  The programmer was using a target class which had a collection property.  His DTL had a loop that was passing the entire target object to a subtransform to have elements added to the collection property, and the result was stored back into target.  That subtransform DTL needed to have create=existing to avoid wiping out the target before appending to the collection.

Brendan Batchelder · Jun 22, 2016 go to post

I reproduced what you're seeing in latest.

If I disable a component via the Production Configuration Page, I see in studio the production class is updated immediately.  It automatically refreshes itself within studio.

When I disable the component using Ens.Director in terminal, the production class is not updated at all.  Even if I close and reopen the production class, it still has the old value.

I took a look at the code and it looks like when settings are modified via the Production Configuration page, method SaveToClass in class Ens.Config.Production gets called (near the end of method SaveSettingsToServer in class EnsPortal.ProductionConfig).  When Ens.Director method EnableConfigItem is called, there is no call to SaveToClass.  I just tested adding that call to the method EnableConfigItem and it worked for me.

So you could add code after your call to Ens.Director method EnableConfigItem to open the Ens.Config.Production object and call the SaveToClass method, passing the Ens.Config.Item object for the component that was just enabled/disabled.  You can take a look at the EnableConfigItem method to see how it gets the Ens.Config.Production object and the Ens.Config.Item object.

Brendan Batchelder · Jun 22, 2016 go to post

You mentioned that when you add a constraint, that's when the rule stops working.

Check to make sure the data in your message matches your constraints:

In the message body properties, DocTypeCategory should match the rule constraint's docCategory.  If they don't match, look at the settings for the business service sending messages to the router and make sure the "Message Schema Category" setting is set correctly.

Also make sure the message body's DocTypeName matches the rule constraint's docName.

Also make sure the message header's SourceConfigName matches the rule constraint's source.

As a side note, when the business service generates the HL7 message object, if the Message Schema Category setting is correct, the service will pull the document type out of MSH:messagetype and store it in DocTypeName for the constraints, so your condition checking MSH:messagetype should be unnecessary.   It's checking the same thing as the constraint.

Brendan Batchelder · Jun 21, 2016 go to post

Thanks for your response.  This wasn't a question, but an article to explain to Ensemble programmers how to resend a large number of Ensemble messages.  It's not related to semaphores.

Brendan Batchelder · Jun 17, 2016 go to post

The REST operation uses adapter EnsLib.HTTP.OutboundAdapter.  This adapter class internally uses %Net.HttpRequest method Send to send the http request.  That method takes a debug argument telling it to just print the HTTP request rather than sending it to the server.  EnsLib.HTTP.OutboundAdapter has a parameter DEBUG which is passed as that debug argument.  One thing you could do is temporarily change this parameter to 1 and run the operation in the foreground (only works on windows instances), which will cause the operation to pass that 1 as the debug argument to %Net.HttpRequest method Send.  This should print out the request to terminal rather than sending it to the server.

One more example: How to call a stored procedure passing a Cache stream as a SQL CLOB:

PROCEDURE PUTSTREAM(STREAMIN IN CLOB) IS

BEGIN

               INSERT INTO QUALITY.STREAMTABLE VALUES(STREAMIN);

END PUTSTREAM;

QUALITY.STREAMTABLE just has one column of type CLOB.

Here is the Ensemble Operation code:

Method SendStream(pRequest As Ens.StreamContainer, Output pResponse As Ens.Response) As %Status
{
               set TestParams=1

               set TestParams(1) = pRequest.Stream
               set TestParams(1,"SqlType")=$$$SqlLongVarchar //EnsSQLTypes.inc
               set TestParams(1,"LOB")=1

               set QueryStatement = "{ call QUALITY.PUTSTREAM(?) }"

               set tSC = ..Adapter.ExecuteProcedureParmArray(.ResultSnapshots, .OutputParams, QueryStatement, , .TestParams)

               Quit tSC
}

I just wanted to add another example to this.  This example shows how to call an oracle stored procedure that uses a SYS_REFCURSOR as an output parameter.

 

  CREATE OR REPLACE PROCEDURE "QUALITY"."BRENDANGETCURSORS" (

 "ONE" IN DECIMAL,

 "TWO" OUT SYS_REFCURSOR) IS

 BEGIN

 OPEN TWO FOR SELECT * FROM QUALITY.BRENDAN WHERE NUM=ONE;

 END "BRENDANGETCURSORS";

 

And the table Quality.Brendan just has a DECIMAL column named NUM and a VARCHAR2 column named STRING

 

Here is the code I used in my Ensemble SQL operation:

 

Method OnMessage(pRequest As Ens.Request, Output pResponse As Ens.Response) As %Status
{
                set tSC = $$$OK
               
                set parms=2
                set parms(1,"IOType")=$$$SQLPARAMINPUT
                set parms(1,"SqlType")=$$$SqlDecimal
                set parms(1,"Prec")=2
                set parms(1)=1
               
                set parms(2,"IOType")=$$$SQLPARAMOUTPUT
                set parms(2,"SqlType")=$$$SqlWLongVarchar
                set parms(2,"LOB")=0
               
               
                set sql="{CALL QUALITY.BRENDANGETCURSORS(?,?)}"
                set rs = ##class(%ListOfObjects).%New()
                set snap = ##class(EnsLib.SQL.Snapshot).%New()
                set snap.MaxRowsToGet=10
                set tSC = rs.Insert(snap)
                quit:$$$ISERR(tSC) tSC
               
                set tSC=..Adapter.ExecuteProcedureParmArray(.rs,.out,sql,"io",.parms)
                quit:$$$ISERR(tSC) tSC
               
                set snap = rs.GetAt(1)
               
                $$$TRACE("Row Count: "_snap.RowCount)
                $$$TRACE("Column Count: "_snap.ColCount)
               
                $$$TRACE("Object? "_$IsObject(snap))
                while ('snap.AtEnd) {
                                $$$TRACE("Num: "_snap.Get("Num"))
                                $$$TRACE("String: "_snap.Get("String"))
                                do snap.%Next(.tSC)
                                quit:$$$ISERR(tSC)
                }

                quit tSC
}
Brendan Batchelder · May 27, 2016 go to post

Ensemble provides a tool for generating HTML or PDF documentation for a production.  This documentation includes all interfaces and any directories they read from or ports they listen on or ip addresses/ports they connect to, all together in a convenient list.

To get to this documentation, on the production configuration page, click the "Production Settings" link which should be just above the Operations column, and then select the actions tab on the right side and there should be a "Document" button.

You mentioned you're also looking for the information seen on the production monitor, such as message counts, but in a simpler view.  There isn't really a more simple view of this, but if you want to try to use SQL to get the data, I would start with the Ens.MessageHeader table.  There is a single Ens.MessageHeader row generated for every message sent from one component to another within a production.