Eduard Lebedyuk · Mar 31, 2017 go to post

Execute this SQL in every namespace you need to get a list of production classes and their descriptions:

SELECT
ID, Description
FROM Ens_Config.Production

Here's a sample project with query that executes in every Ensemble namespace. Also article about writing custom queries.

Eduard Lebedyuk · Mar 31, 2017 go to post

Here's a sample BP that calls BO asynchronously. BO returns  current request state, upon which BP decides to wait some more, report an error or process an answer:

Class test.BP Extends Ens.BusinessProcess
{

/// Operation name
Property Operation As %String(MAXLEN = 250) [ Required ];

/// How long to wait for an answer. 0 - forever.
Property MaxProcessTime As %Integer(MINVAL = 0) [ InitialExpression = 3600 ];

Parameter SETTINGS = "MaxProcessTime:Basic,Operation:Basic:selector?context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId}";

/// Identifier for a first request
Parameter callCOMPLETIONKEY = "*call*";

/// Identifier for a state request
Parameter getStateCOMPLETIONKEY = "*getState*";

/// Alarm identifier
Parameter alarmCOMPLETIONKEY = "*alarm*";

Method OnRequest(pRequest As Ens.StringRequest, Output pResponse As Ens.StringResponse) As %Status
{
    #dim msg As Ens.StringRequest = pRequest.%ConstructClone(1)
    quit ..SendRequestAsync(..Operation, msg, $$$YES, ..#callCOMPLETIONKEY)
}

/// Process Async reply from Operation
Method OnResponse(request As Ens.StringRequest, ByRef response As Ens.StringResponse, callrequest As Ens.StringRequest, callresponse As Ens.StringResponse, pCompletionKey As %String) As %Status
{
    #dim sc As %Status = $$$OK
    
    // Got an error
    if $$$ISERR(callresponse.status)
    {
        set response = callresponse
        quit $$$OK
    }
    
    // Got primary answer
    if (pCompletionKey = ..#callCOMPLETIONKEY) || (pCompletionKey = ..#alarmCOMPLETIONKEY)
    {
        quit ..OnResponseFromCallOrAlarm(request, .response, callrequest, callresponse, pCompletionKey)
    }
    
    // Got getState
    if (pCompletionKey = ..#getStateCOMPLETIONKEY)
    {
        
        // Current processing state (1 - received; 2 - in work; 3 - done)
        #dim status As %String = callresponse.StringValue

        // If not 3, run getState again
        if (status '= "3")
        {        
            // Check how much time passed since we started
            set processTime = $system.SQL.DATEDIFF("s", ..%TimeCreated, $$$ts)
            
            if ((..MaxProcessTime=0) || (processTime<..MaxProcessTime)) {
                // Let's run getState again in 30 seconds
                #dim alarmMsg As Ens.AlarmRequest = ##class(Ens.AlarmRequest).%New()
                set alarmMsg.Duration = "PT30S"
                quit ..SendRequestAsync("Ens.Alarm", alarmMsg, $$$YES, ..#alarmCOMPLETIONKEY)
            } else {
                // Timeout
                set response = ##class(Ens.StringResponse).%New()
                set response.status = $$$ERROR($$$GeneralError, "Timeout")
                quit $$$OK    
            }
        }
        else
        {
            quit ..OnResponseFromGetState3(request, .response, callrequest, callresponse, pCompletionKey)
        }
    }
    
    // unrecognized pCompletionKey value
    quit $$$ERROR($$$InvalidArgument)
}

/// OnResponse for alarm or call - run get state
Method OnResponseFromCallOrAlarm(request As Ens.StringRequest, ByRef response As Ens.StringResponse, callrequest As Ens.StringRequest, callresponse As Ens.StringResponse, pCompletionKey As %String) As %Status [ Private ]
{
    #dim msg As Ens.StringRequest = pRequest.%ConstructClone(1)
    quit ..SendRequestAsync(..Operation, msg, $$$YES, ..#getStateCOMPLETIONKEY)
}

/// OnResponse for an answer
Method OnResponseFromGetState3(request As Ens.StringRequest, ByRef response Ens.StringResponse, callrequest As Ens.StringRequest, callresponse As Ens.StringResponse, pCompletionKey As %String) As %Status [ Private ]
{
    // Process complete response
    quit $$$OK
}

OnResponse would be an automatic observer, and can in turn notify anything else. You can use original request id or SessionID to identify what you need to notify.

Eduard Lebedyuk · Mar 30, 2017 go to post

What's the status of:

Set tSC = ##class(Ens.Director).EnableConfigItem(pConfigItemName,0,1)
Eduard Lebedyuk · Mar 30, 2017 go to post

What is "productdescription"? How do you access it now? Use this code to switch namespaces:

new $namespace
set $namespace = <Other namespace>
do stuff

Docs for $namespace, zn.

Eduard Lebedyuk · Mar 29, 2017 go to post

From the class documentation on OSUserName:

Operating system username of process.
Username given to the process by the operating system when the process
is created. When displayed, it is truncated to 16 characters. Note that the real O/S
username is only returned when connecting to UNIX or VMS systems; For Windows, it
will return the O/S username for a console process, but for telnet it will return
the $USERNAME of the process. For client connections, it contains the O/S username
of the client. This field is truncated at 16 characters.

_Ensemble is a Caché user under which all Ensemble jobs are run.

If you need to understand the context under which the service runs execute in a BS:

do $zf(-1,"set > vars.txt")

to output all environment variables to a file.

Eduard Lebedyuk · Mar 29, 2017 go to post

There is no Cache function to do that.

But as PDF contains readable "postscript" parts you can use regexp to search for relevant information. Stack. Article. It's not guaranteed to be precise though.

Here's my article about using LibreOffice for work with documents. I've also used ghostscript and postscript to work with pdf from Caché and it's all fairly straightforward.

Also, here's the code I wrote (execute is defined here) to add footer to every page of a PDF file using ghostscript:

/// Use ghostscript (%1) to apply postscript script %3
/// Upon source pdf (%4) to get output pgf (%2)
/// Attempts at speed  -dProvideUnicode -dEmbedAllFonts=true  -dPDFSETTINGS=/prepress
Parameter STAMP = "%1  -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=%2 %3 -f %4";

ClassMethod stampPDF(pdf, psFile, pdfOut) As %Status
{
    set cmd = $$$FormatText(..#STAMP, ..getGS(), pdfOut, psFile, pdf)
    return ..execute(cmd)
}

ClassMethod createPS(psFile, text) As %Status
{
    set stream = ##class(%Stream.FileCharacter).%New()
        // For cyrillic text
    set stream.TranslateTable = "CP1251"
    set sc = stream.LinkToFile(psFile)
    quit:$$$ISERR(sc) sc
    
    do stream.WriteLine("<<")
    do stream.WriteLine("   /EndPage")
    do stream.WriteLine("   {")
    do stream.WriteLine("     2 eq { pop false }")
    do stream.WriteLine("     {")
    do stream.WriteLine("         gsave")
    do stream.WriteLine("         /MyFont 12 selectfont")
    do stream.WriteLine("         30 70 moveto (" _ text _ ") show")
    do stream.WriteLine("         grestore")
    do stream.WriteLine("         true")
    do stream.WriteLine("     } ifelse")
    do stream.WriteLine("   } bind")
    do stream.WriteLine(">> setpagedevice")
    
    quit stream.%Save()
}

/// Get gs binary
ClassMethod getGS()
{
    if $$$isWINDOWS {
        set gs = "gswin64c"
    } else {
        set gs = "gs"
    }
    return gs
}

Ghostscript can be used to get a number of pages in a PDF file. Here's how.

Eduard Lebedyuk · Mar 28, 2017 go to post

In my case I need everything in the url as one argument. But that's also useful for some use cases.

Eduard Lebedyuk · Mar 28, 2017 go to post

Thank you. That helped.

Class Try.REST Extends %CSP.REST
{

XData UrlMap
{
<Routes>
    <Route Url="(.*)" Method="GET" Call="GET"/>
 </Routes>
}

ClassMethod GET(href)
{
    w "OK"
    s ^dbg($i(^dbg)) = $g(href)
    q 1
}

}
Eduard Lebedyuk · Mar 28, 2017 go to post

Can documentation be expanded to include information about SQL search. I always forget&rediscover where things are and especially how they are related

Eduard Lebedyuk · Mar 27, 2017 go to post

Check UserAction method of %Studio.Extension.Base class. You can call CSP pages from there.

Eduard Lebedyuk · Mar 27, 2017 go to post

In your BO try:

Set result = ..Adapter.FTP.MakeDirectory(.path)

Adapter is EnsLib.FTP.OutboundAdapter and FTP is %Net.FtpSession.

I would subclass the adapter and add MakeDirectory method there, with code pointing to the FTP method and status conversion.

Eduard Lebedyuk · Mar 25, 2017 go to post

It's a valid sample. You just can't write directly into ZEN context. Well, you can, obviously, but that causes errors.

Eduard Lebedyuk · Mar 24, 2017 go to post

1. https certificate would apply to the cache and csp sites defined under IIS.  It would apply  to everyrhing really.

2. Not sure about html landing page being a security threat. That depends on your setup. Do you embed http parts in your (future) https pages?

I'd recommend as a first step to install let's encrypt certificate - it's free and easy.

Then force http->https redirect on your iis server.

After that check how your html landing page behaves.

Eduard Lebedyuk · Mar 24, 2017 go to post

I think the issue of compilation order specification is more at fault here. Full compile from scratch (on a new instance) or a full recompile should work without errors every time. If it's not, set DependsOn keyword strategically.

Eduard Lebedyuk · Mar 24, 2017 go to post

Thank you! That's very useful.

Is there a way to return real implementation class?

For example, consider these classes:

Class App.Use {

ClassMethod Test()
{
    w 1/0
}
}

and:

Class App.Use2 Extends App.Use
{
}

If I call:

do ##class(App.Use2).Test()

I get  the following CLS source line:

App.Use2:Test+1

Yet, the relevant code is actually implemented in

App.Use:Test+1

One approach I see is checking %Dictionary.MethodDefinition recursively till I find a method definition, but there's bound to be many problems multiple inheritance.

Eduard Lebedyuk · Mar 24, 2017 go to post

I don't understand your question. Please elaborate on the following points:

  1. Do you want to create arbitrary relationships programmatically at design time (possible) or at runtime (not really)?
  2. What difficulties in managing relationships you have encountered?
  3. Can you provide some mock code demonstrating what you want to achieve?
Eduard Lebedyuk · Mar 23, 2017 go to post

Because I want it done automatically. I already wrote several converters manually as described, but started thinking about automating the task.

Eduard Lebedyuk · Mar 22, 2017 go to post

Here's how to view stream properties in SQL.  Alternatively you can use %CONTAINS to search the stream.

Another way would be writing an sql procedure that gets stream, converts it to object  and checks if it has the token. Would be slow though. Maybe do a simple [ check first and then pass only those rows that have the token into sql procedure?

Can you parse the stream when you receive the message?