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)

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.

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.

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.

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" ];

}

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.

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)

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";

I'd suggest contacting the WRC to help look at the problem you're seeing with unwanted characters in the output. EnsLib.RecordMap.Operation.BatchFileOperation does what you've described, so it would be best to get this working for your use case if possible.

Looking at the code you posted...

pRequest.Records.GetAt(1)

This will just get you the first record from the batch. If you want to get a specific field from a record you would want to use something like:

pRequest.Records.GetAt(1).FieldName

One thing I see is that you don't need a value for the "key" attribute in the assign for callrequest.EOBList.  Key is used to assign the value to a specific member of an array/list whereas it looks like you want to assign the entire list.

Does this work better?
<
assign property="callrequest.EOBListvalue="context.tEOBListaction="set" key="/>

Are StageBatchId and Filename getting passed correctly?

Hi Lorraine,

A few questions to help us get a clearer picture:

  • Which component is generating the 5005 error? Is it the router or the business operation?
  • Can you post a screenshot or the source code for your routing rule?
  • Is your router calling a transformation or just passing the file through unchanged?
  • What happens if you remove all logic from your router and just change it to do a simple "send" with no transformation?

-Marc

It sounds like you might be able to avoid the problem of converting to XML and creating an EnsLib.EDI.XML.Document if you were able to access fields in your persistent message class from your routing rule logic?

If your persistent message class has discrete properties, you can still refer to these directly in a routing rule condition even without the VDoc style GetValueAt().

If your message object has references to repeating child objects and you need to get to a deeper level such as checking a property of the Nth child object you can do this by creating a custom function and passing it the Document object as a parameter.

Does it need to be a binary executable or would a batch file work? It is possible to execute a Caché routine/method from a Windows command line or batch file by invoking the Caché binary and passing it the name of what you want to execute.

The docs give this example for freezing and thawing the database for backups:
http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=...
 

CD C:\InterSystems\E20131\mgr\
..\bin\cache -s. -B -V -U%SYS ##Class(Backup.General).ExternalThaw()

The high-level steps are:

  1. Create a Java class using the APIs provided by your JMS server (ActiveMQ, RabbitMQ, etc.) and create a JAR file. JMS is a standardized API, not a protocol, so there is not one universal JMS client that will work with all JMS servers.
  2. Use the Java Gateway wizard to generate COS proxy classes for your Java class
  3. Setup a Java Gateway Service in your production. Make sure it points to your custom JAR file and the JAR file provided by the JMS server vendor.
  4. Create a custom business service or operation that calls the COS proxy class methods.
  5. When adding the service or operation to the production, configure it to point to the Java Gateway Service and use the correct Java class

For a Business Operation, you'll just use the usual approach: the message map points to a custom method. The custom method then calls the COS proxy class methods for your Java class. The BO class should extend Ens.BusinessOperation and use the adapter EnsLib.JavaGateway.OutboundAdapter.

For a Business Service, there's an out of the box adapter, EnsLib.JavaGateway.InboundAdapter. You just need to create a custom Operation that extends Ens.Business service and uses EnsLib.JavaGateway.InboundAdapter as it's adapter. This is quite nice, because EnsLib.JavaGateway.InboundAdapter handles most everything automatically. Your Java class just needs to implement a set of methods defined by the Inbound Adapter. See the class reference for EnsLib.JavaGateway.InboundAdapter for more details:
 *             .Connect(pJGClientConnectSpec,pTimeout,pInbound)
 *             .Disconnect(pInbound)
 *             .IsConnected(pInbound)
 *             .getErrorText() [returns empty string if no error on previous method call]
 *             .ReceiveMessage(pTimeout) [inbound only; pMsgIn return needs not be populated if timed out]
 *             .SendResponse(pMsgOut) [inbound only, optional-needed if OnProcessInput will return an object]
 *             .SendMessage(pMsgOut,pGetReply,pResponseTimeout) [outbound only; pMsgIn return needs not be populated if no responses are anticipated]

I've created some sample code including a production and Java classes. It includes a generic class that implements most of the necessary methods using standard JMS API methods and provides an ActiveMQ-specific subclass that uses the ActiveMQ proprietary methods to initiate the connection.

As always, this is sample code for demonstration purposes only and is not production ready.