Every routines stores directly in database, so, you can't just open it as any file on your filesystem as well as you do it with csp files.

but you can open it with %RoutineMgr class, something like this

USER>zn "samples"

SAMPLES>set rtn=##class(%RoutineMgr).%OpenId("hello.mac")

SAMPLES>while 'rtn.Code.AtEnd { write !,rtn.Code.ReadLine()}
hello ; hello world routine
 write !, "hello world"
 write !, "bye"
end quit  ; end

if you also need to get list of such files, you can use query StudioOpenDialog in %RoutineMgr class

SAMPLES>set st=##class(%SQL.Statement).%New()
SAMPLES>set sc=st.%PrepareClassQuery("%RoutineMgr","StudioOpenDialog")
SAMPLES>set rs=st.%Execute("*.mac")
SAMPLES>do rs.%Display()
Dumping result #1
Name    IsDirectory     Type    Size    Date    Description     IconType
DocBook .       9                               0
badroutine.mac          0       62      2004-09-28 13:05:45             0
CinemaData.mac          0       10258   2016-05-10 22:32:04             0
compareloop.mac         0       1201    2004-12-02 18:23:57             0
datent.mac              0       3089    2002-01-03 12:05:01             0
datentobj.mac           0       2627    2002-09-06 00:15:23             0
dbconvert.mac           0       1532    2002-01-03 12:05:52             0
fibonacci.mac           0       365     2002-01-03 12:06:03             0
forexample.mac          0       502     2004-11-30 16:00:08             0
funcexample.mac         0       333     2002-01-03 12:06:30             0
hello.mac               0       251     2016-07-13 12:07:04.520803              0
LDAP.mac                0       68720   2013-12-16 11:48:09.962335              0
lookup.mac              0       8484    2008-08-21 19:17:48             0
lookup1.mac             0       1465    2002-01-03 12:07:15             0
lookup2.mac             0       5984    2002-01-07 18:08:46             0
lookupobj.mac           0       7857    2008-08-21 21:58:25             0
loopend.mac             0       242     2002-01-03 12:08:12             0
loopstart.mac           0       217     2002-01-03 12:08:21             0
nameloop.mac            0       552     2002-01-03 12:08:33             0
passbyref.mac           0       604     2002-01-07 12:01:47             0
postcond.mac            0       341     2002-01-03 12:08:51             0
procexample.mac         0       423     2002-01-03 12:08:59             0
publicvarsexample.mac           0       357     2002-01-03 12:09:09             0
RightTriangle.mac               0       1836    2011-02-24 18:56:29             0
root.mac                0       149     2004-11-30 15:57:29             0
simpleloop.mac          0       161     2002-01-03 12:09:59             0
SQLGatewayTest.mac              0       2480    2016-05-10 22:32:04             0
survivor.mac            0       179     2002-01-03 12:10:32             0
ZAUTHENTICATE.mac               0       37302   2015-03-10 10:48:43.589807              0
ZAUTHORIZE.mac          0       12120   2016-05-10 22:32:04             0
30 Rows(s) Affected

but WaitMsg it is on parent's side, my control code ususaly like like this

    set resName="someresource"
    job ..someJob(jobsData,resName)
    set child=$zchild
    for {
        set $lb(sc,data)=$system.Event.WaitMsg(resName, 1)
        if sc<=0 {
        // do some staff with incomming data

But if you want to interrupt your child process, of course will be possible only from such process, if your process have some loops you may just check if parent still exists, if not, just stop working.


I would recommend to distant webservers from ECP Application servers. In your case I would install webserver exactly on the server where ECP Application Server have already installed, so in this case your connect via superport will be secured, and in this case only web port should be opened in the firewall. Outside of this servers after firewall you need to use some load balancer, in this case I would recommend to use HAProxy. Connection between extarnal HAProxy and  internal webservers could be secured with ssl, but I'm not sure that is really needed.

And in this case it is possible to store all static content such as JS, CSS and images on server where load balancer placed, to avoid redundant request to the data servers. It is not a big problem for the production systems.

So finally in simple view it my looks like below.

Well, you can't write conditions in this way, and compile error which you see, it is actually parsing error for such condition
How you can change it, You should define some function which may do all this check or partially, just return some value for check.

To define such function, you should have your child for Ens.Rule.FunctionSet class, something like this

Class Test.Utils Extends Ens.Rule.FunctionSet

ClassMethod GetAt(value As %String = "", index As %String) As %String
    if $isobject(value) {
        quit value.GetAt(index)
    quit ""


and then, you can use it in this way, where GetAt your just created function.

<rule name="">
<constraint name="msgClass" value="Test.TestMessage"></constraint>
<when condition="GetAt(Document.myList,1)=&quot;AA&quot;">
<send transform="" target="DummyOperation"></send>

But your function should return final value for checking, and you can't use write so,


I use such code in one of my projects

ClassMethod Test()
    set address="One Memorial Drive, Cambridge, MA 02142, USA"
    do ..GetCoords(address, .latitude, .longitude, .partialMatch)
    zw latitude, longitude, partialMatch

ClassMethod GetCoords(Address As %String, Output Latitute As %Float = "", Output Longitude As %Float = "", Output PartialMatch As %Boolean) As %Status
    set params("address")=Address
    set sc=..CallGoogleAPI("/maps/api/geocode/json", .params, .data)
    if data.status="OK" {
        set result=data.results.$get(0)
        set PartialMatch = +result."partial_match"
        set Latitute = result.geometry.location.lat
        set Longitude = result.geometry.location.lng
    quit $$$OK

ClassMethod CallGoogleAPI(Url As %String, ByRef Params, Output Data) As %Status
    #;set Params("key")="your api key here"
    quit ..CallApi("maps.googleapis.com", 1, Url, .Params, .Data)

ClassMethod CallApi(Server As %String, Secure As %Boolean = 1, Url As %String, ByRef Params, Output Data) As %Status
    set ht=##class(%Net.HttpRequest).%New()
    set ht.Server="maps.googleapis.com"
    set ht.Https=Secure
    set:Secure ht.SSLConfiguration=..GetSSLCertificate(Server)
    set param=""
    for {
        set param=$order(Params(param),1,value)
        do ht.SetParam(param, value)
    set sc=ht.Get(Url)
    if $$$ISERR(sc) quit sc
    set Data={}
    set Data=Data.$fromJSON(ht.HttpResponse.Data)
    quit $$$OK

ClassMethod GetSSLCertificate(Server As %String) As %Status
    new $namespace
    znspace "%SYS"
    do {
        set tSC=##class(Security.SSLConfigs).Create(Server)
    } while 0
    quit Server

Much better If it would be possible to migrate or merge two accounts here. I have a WRC account on email from my current company, but what happens when I chnage a job. In this case I may loose access to any my content here. And I have another registration with my own email. It would be very nice if I could merge two of this accounts or something else, if I could use WRC and commonity with the same my own not corporate account.

Hi Kenneth,

I've been working in company where we develop WEB-based ECM application. It's a quite big one, and very flexible.  We do not use CSP-files at all, only CSP-classes, no Zen. And some my own projects on Caché, also in WEB. I will not recommend to use CSP-files, or Zen. I can't say anything about Zen Mojo, just because when I last time saw it, it was not so better then Zen, now with Native JSON it may be better.  I'll recommend CSP-classes, REST and webservices.

CSP-Pages. It may be usefull, with all of their tags and stores with files. But unfortunately  all of this files, may some time confuse developer, because in any way developer should know is it compiled or not, when some files were changed, it may looks like nothing changed.  And at the end anyway it compiles to CSP-classes. 

ZEN. With ZEN, you get some ready components. In the same time, your application became too big, even if it not, and have just one page. It generate js/css files, and you should care about that files, while you deploy app to production. Data transfered from server to client side, contains waste js code.

With CSP-classess, I have flexibility, and can generate different parts of page in very different parts of code. I have much more possibilities to control how I generate HTML, receive and response requests. But I still generate html-code in Caché.

As well we have more than 1MB CSS and JS files. For me it is not so comfortably to edit that files in Studio. For HTML in classes I can use embedded &html<>. JS and CSS files I'm editing in Sublime Text. 

But I'm sure that in modern time application should be SPA, all html should be in static files, and server should speak only in JSON, with a REST and wevservices. And in all my last web-projects, I'm using this way.  Static HTML, JS, CSS files, with a Gulp I transform all sperate JS and CSS files to one, implement that files to HTML. And for production version I put all that files in one Class. In this case I can send one XML file which contains full application.  For example CacheBlocksExplorer

You can calculate such property on a class side, something like this. Index by CitizenRef and RelocationDate, and method which looking for the next date, and returns true if nothing found.

Index CitizenRelocation On (CitizenRef, RelocationDate);

Property IsLastKnownRecord As %Boolean [ Calculated, SqlComputeCode = {set {*}=##class({%%CLASSNAME}).IsLastKnownRecordCheck({CitizenRef}, {RelocationDate})}, SqlComputed ];

Method IsLastKnownRecordGet() As %Boolean
    quit ..IsLastKnownRecordCheck(i%CitizenRef, i%RelocationDate)

ClassMethod IsLastKnownRecordCheck(CitizenRef As %Integer, RelocationDate As %Date) As %Boolean [ CodeMode = objectgenerator ]
    set storagename="Default"
    set storageInd=%class.Storages.FindObjectId(%classname_"||"_storagename)
    set storage=%class.Storages.GetAt(storageInd)
    set indexLocation=storage.IndexLocation
    set indexLocation=$name(@indexLocation@("CitizenRelocation"))
    do %code.WriteLine($c(9)_"quit $order(@($name("_indexLocation_"))@(CitizenRef, RelocationDate))=""""")
    quit $$$OK

And result you can see below