This article is a translation of my article published on the new InterSystems Developer Community portal. It tells about another possibility in Studio - support for autocompletion when creating XML documents in XData. This article develops the idea raised by Albert Fuentes about the use of XData and code generators to simplify the creation of certain rules. You may have already encountered autocompletion in XData when developing a ZEN application, % Installer- manifest or REST broker. This is called Studio Assist . I will tell you how to configure and use this feature.# This file defines the Rest UrlMap studio assist database # Define the prefix mapping !prefix-mapping:urlmap:http://www.intersystems.com/urlmap # Set the default namespace to urlmap !default-namespace:http://www.intersystems.com/urlmap # Set the default prefix for element definitions that follow !default-prefix:urlmap /#Routes Routes/#Map Routes/#Route Map|Prefix Map|Forward Route|Url Route|Method@enum:!,GET,HEAD,POST,PUT,DELETE,TRACE,CONNECT Route|Call Route|Cors@enum:!,true,false XData XMLData [ XMLNamespace = RuleEngine ] { <Definition Identifier="PatientAlerts"> <Rule Title="Not young anymore!" Condition="context.Patient.DOB > $horolog-30"> <Action Type="call" Class="IAT.RuleEngine.Test.Utils" Method="SendEmail" Args=""test@server.com","Patient is so old!""/> <Action Type="call" Class="IAT.RuleEngine.Test.Utils" Method="ShowObject" Args="context.Patient"/> <Action Type="return"/> </Rule> </Definition> } Class IAT.RuleEngine.Definition Extends %XGEN.AbstractDocument [ System = 3 ] { Parameter NAMESPACE = "RuleEngine"; Parameter XMLNAMESPACE = "RuleEngine"; Parameter ROOTCLASSES As STRING = "IAT.RuleEngine.Definition:Definition"; Property Identifier As %String(MAXLEN = 200, XMLPROJECTION = "ATTRIBUTE"); Property Rules As list Of Rule(XMLPROJECTION = "ELEMENT"); /// This method is called when a class containing an XGEN /// document is compiled. It is called <em>before</em> the <method>%GenerateCode</method> method /// processes its children.<br> /// <var>pTargetClass</var> is the class that contains the XGEN document.<br/> /// <var>pCode</var> is a stream containing the generated code.<br/> /// <var>pDocument</var> is the top-level XGEN document object that contains this node.<br/> /// A subclass can provide an implementation of this method that will /// generate specific lines of code.<br/> Method %OnBeforeGenerateCode(pTargetClass As %Dictionary.CompiledClass, pCode As %Stream.TmpCharacter, pDocument As %XGEN.AbstractDocument) As %Status { do pCode.WriteLine("#define AddLog(%line) set log($i(log))=""[""_$zdatetime($ztimestamp,3)_""] ""_%line") do pCode.WriteLine(..%Indent(1)_"Set tSC = $$$OK ") do pCode.WriteLine(..%Indent(1)_"try { ") quit $$$OK } /// This method is called when a class containing an XGEN /// document is compiled. It is called <em>after</em> the <method>%GenerateCode</method> method /// processes its children.<br> /// <var>pTargetClass</var> is the class that contains the XGEN document.<br/> /// <var>pCode</var> is a stream containing the generated code.<br/> /// <var>pDocument</var> is the top-level XGEN document object that contains this node.<br/> /// A subclass can provide an implementation of this method that will /// generate specific lines of code.<br/> Method %OnAfterGenerateCode(pTargetClass As %Dictionary.CompiledClass, pCode As %Stream.TmpCharacter, pDocument As %XGEN.AbstractDocument) As %Status { do pCode.WriteLine(..%Indent(1)_"} catch ex { set tSC = ex.AsStatus() }") do pCode.WriteLine(..%Indent(1)_"quit tSC") quit $$$OK } } Class IAT.RuleEngine.Rule Extends IAT.RuleEngine.Sequence [ System = 3 ] { Property Title As %String(XMLPROJECTION = "ATTRIBUTE"); Property Condition As %String(XMLPROJECTION = "ATTRIBUTE"); Property Actions As list Of Action(XMLPROJECTION = "ELEMENT"); /// This method is called when a class containing an XGEN /// document is compiled. It is called <em>before</em> the <method>%GenerateCode</method> method /// processes its children.<br> /// <var>pTargetClass</var> is the class that contains the XGEN document.<br/> /// <var>pCode</var> is a stream containing the generated code.<br/> /// <var>pDocument</var> is the top-level XGEN document object that contains this node.<br/> /// A subclass can provide an implementation of this method that will /// generate specific lines of code.<br/> Method %OnBeforeGenerateCode(pTargetClass As %Dictionary.CompiledClass, pCode As %Stream.TmpCharacter, pDocument As %XGEN.AbstractDocument) As %Status { do pCode.WriteLine(..%Indent()_"If ("_..Condition_") { set actionCounter=0 ") do pCode.WriteLine(..%Indent(1)_"$$$AddLog(""Rule: "_..Title_" "")") quit $$$OK } /// This method is called when a class containing an XGEN /// document is compiled. It is called <em>after</em> the <method>%GenerateCode</method> method /// processes its children.<br> /// <var>pTargetClass</var> is the class that contains the XGEN document.<br/> /// <var>pCode</var> is a stream containing the generated code.<br/> /// <var>pDocument</var> is the top-level XGEN document object that contains this node.<br/> /// A subclass can provide an implementation of this method that will /// generate specific lines of code.<br/> Method %OnAfterGenerateCode(pTargetClass As %Dictionary.CompiledClass, pCode As %Stream.TmpCharacter, pDocument As %XGEN.AbstractDocument) As %Status { do pCode.WriteLine(..%Indent()_"}") quit $$$OK } } Class IAT.RuleEngine.Action Extends IAT.RuleEngine.RuleEngineNode [ System = 3 ] { Parameter NAMESPACE = "RuleEngine"; Property Type As %String(VALUELIST = ",call,return", XMLPROJECTION = "ATTRIBUTE"); Property Class As %String(XMLPROJECTION = "ATTRIBUTE"); Property Method As %String(XMLPROJECTION = "ATTRIBUTE"); Property Args As %String(XMLPROJECTION = "ATTRIBUTE"); /// Generate code for this node.<br/> /// This method is called when a class containing an XGEN /// document is compiled.<br/> /// <var>pTargetClass</var> is the class that contains the XGEN document.<br/> /// <var>pCode</var> is a stream containing the generated code.<br/> /// <var>pDocument</var> is the top-level XGEN document object that contains this node.<br/> /// A subclass will provide an implementation of this method that will /// generate specific lines of code.<br/> /// For example: /// <example> /// Do pCode.WriteLine(..%Indent()_"Set " _ ..target _ "=" _ $$$quote(..value)) /// </example> Method %OnGenerateCode(pTargetClass As %Dictionary.CompiledClass, pCode As %Stream.TmpCharacter, pDocument As %XGEN.AbstractDocument) As %Status { do pCode.WriteLine(..%Indent()_"$$$AddLog(""Action: ""_$i(actionCounter))") if ..Type="call" { do pCode.WriteLine(..%Indent() _ "do $classmethod("_$$$quote(..Class)_", "_$$$quote(..Method)_", "_..Args_")") } elseif ..Type="return" { do pCode.WriteLine(..%Indent() _ "quit ") } Quit $$$OK } } Class IAT.RuleEngine.Engine Extends %RegisteredObject [ System = 3 ] { XData XMLData [ XMLNamespace = RuleEngine ] { <Definition> </Definition> } /// ClassMethod Evaluate(context, log) [ CodeMode = objectgenerator ] { /// Quit ##class(IAT.RuleEngine.Definition).%Generate(%compiledclass, %code, "XMLData") } } Class IAT.RuleEngine.Test.PatientAlertsRule Extends IAT.RuleEngine.Engine { XData XMLData [ XMLNamespace = RuleEngine ] { <Definition Identifier="PatientAlerts"> <Rule Title="Not young anymore!" Condition="context.Patient.DOB > $horolog-30"> <Action Type="call" Class="IAT.RuleEngine.Test.Utils" Method="SendEmail" Args=""test@server.com","Patient is so old!""/> <Action Type="call" Class="IAT.RuleEngine.Test.Utils" Method="ShowObject" Args="context.Patient"/> <Action Type="return"/> </Rule> </Definition> } } zEvaluate(context,log) public { // generated by IAT.RuleEngine.Definition set tSC=1 try { If (context.Patient.DOB > $horolog-30) { set actionCounter=0 set log($i(log))="["_$zdatetime($ztimestamp,3)_"] "_"Rule: Not young anymore! " set log($i(log))="["_$zdatetime($ztimestamp,3)_"] "_"Action: "_$i(actionCounter) do $classmethod("IAT.RuleEngine.Test.Utils", "SendEmail", "test@server.com","Patient is so old!") set log($i(log))="["_$zdatetime($ztimestamp,3)_"] "_"Action: "_$i(actionCounter) do $classmethod("IAT.RuleEngine.Test.Utils", "ShowObject", context.Patient) set log($i(log))="["_$zdatetime($ztimestamp,3)_"] "_"Action: "_$i(actionCounter) quit } } catch ex { set tSC = ex.AsStatus() } quit tSC } Class IAT.RuleEngine.EngineFile Extends %Studio.AbstractDocument [ System = 4 ] { Projection RegisterExtension As %Projection.StudioDocument(DocumentDescription = "RuleEngine file", DocumentExtension = "RULE", DocumentNew = 0, DocumentType = "xml", XMLNamespace = "RuleEngine"); Parameter NAMESPACE = "RuleEngine"; Parameter EXTENSION = ".rule"; Parameter DOCUMENTCLASS = "IAT.RuleEngine.Engine"; ClassMethod GetClassName(pName As %String) As %String [ CodeMode = expression ] { $P(pName,".",1,$L(pName,".")-1) } /// Load the routine in Name into the stream Code Method Load() As %Status { Set tClassName = ..GetClassName(..Name) Set tXDataDef = ##class(%Dictionary.XDataDefinition).%OpenId(tClassName_"||XMLData") If ($IsObject(tXDataDef)) { do ..CopyFrom(tXDataDef.Data) } Quit $$$OK } /// Compile the routine Method Compile(flags As %String) As %Status { Set tSC = $$$OK If $get($$$qualifierGetValue(flags,"displaylog")){ Write !,"Compiling document: " _ ..Name } Set tSC = $System.OBJ.Compile(..GetClassName(..Name),.flags,,1) Quit tSC } /// Delete the routine 'name' which includes the routine extension ClassMethod Delete(name As %String) As %Status { Set tSC = $$$OK If (..#DOCUMENTCLASS'="") { Set tSC = $System.OBJ.Delete(..GetClassName(name)) } Quit tSC } /// Lock the class definition for the document. Method Lock(flags As %String) As %Status { If ..Locked Set ..Locked=..Locked+1 Quit $$$OK Set tClassname = ..GetClassName(..Name) Lock +^oddDEF(tClassname):0 If '$Test Quit $$$ERROR($$$CanNotLockRoutineInfo,tClassname) Set ..Locked=1 Quit $$$OK } /// Unlock the class definition for the document. Method Unlock(flags As %String) As %Status { If '..Locked Quit $$$OK Set tClassname = ..GetClassName(..Name) If ..Locked>1 Set ..Locked=..Locked-1 Quit $$$OK Lock -^oddDEF(tClassname) Set ..Locked=0 Quit $$$OK } /// Return the timestamp of routine 'name' in %TimeStamp format. This is used to determine if the routine has /// been updated on the server and so needs reloading from Studio. So the format should be $zdatetime($horolog,3), /// or "" if the routine does not exist. ClassMethod TimeStamp(name As %String) As %TimeStamp { If (..#DOCUMENTCLASS'="") { Set cls = ..GetClassName(name) Quit $ZDT($$$defClassKeyGet(cls,$$$cCLASStimechanged),3) } Else { Quit "" } } /// Return 1 if the routine 'name' exists and 0 if it does not. ClassMethod Exists(name As %String) As %Boolean { Set tExists = 0 Try { Set tClass = ..GetClassName(name) Set tExists = ##class(%Dictionary.ClassDefinition).%ExistsId(tClass) } Catch ex { Set tExists = 0 } Quit tExists } /// Save the routine stored in Code Method Save() As %Status { Write !,"Save: ",..Name set tSC = $$$OK try { Set tClassName = ..GetClassName(..Name) Set tClassDef = ##class(%Dictionary.ClassDefinition).%OpenId(tClassName) if '$isObject(tClassDef) { set tClassDef = ##class(%Dictionary.ClassDefinition).%New() Set tClassDef.Name = tClassName Set tClassDef.Super = ..#DOCUMENTCLASS } Set tIndex = tClassDef.XDatas.FindObjectId(tClassName_"||XMLData") If tIndex'="" Do tClassDef.XDatas.RemoveAt(tIndex) Set tXDataDef = ##class(%Dictionary.XDataDefinition).%New() Set tXDataDef.Name = "XMLData" Set tXDataDef.XMLNamespace = ..#NAMESPACE Set tXDataDef.parent = tClassDef do ..Rewind() do tXDataDef.Data.CopyFrom($this) set tSC = tClassDef.%Save() } catch ex { } Quit tSC } Query List(Directory As %String, Flat As %Boolean, System As %Boolean) As %Query(ROWSPEC = "name:%String,modified:%TimeStamp,size:%Integer,directory:%String") [ SqlProc ] { } ClassMethod ListExecute(ByRef qHandle As %Binary, Directory As %String = "", Flat As %Boolean, System As %Boolean) As %Status { Set qHandle = "" If Directory'="" Quit $$$OK // get list of classes Set tRS = ##class(%Library.ResultSet).%New("%Dictionary.ClassDefinition:SubclassOf") Do tRS.Execute(..#DOCUMENTCLASS) While (tRS.Next()) { Set qHandle("Classes",tRS.Data("Name")) = "" } Quit $$$OK } ClassMethod ListFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = ListExecute ] { Set qHandle = $O(qHandle("Classes",qHandle)) If (qHandle '= "") { Set tTime = $ZDT($$$defClassKeyGet(qHandle,$$$cCLASStimechanged),3) Set Row = $LB(qHandle _ ..#EXTENSION,tTime,,"") Set AtEnd = 0 } Else { Set Row = "" Set AtEnd = 1 } Quit $$$OK } /// Return other document types that this is related to. /// Passed a name and you return a comma separated list of the other documents it is related to /// or "" if it is not related to anything<br> /// Subclass should override this behavior for non-class based editors. ClassMethod GetOther(Name As %String) As %String { If (..#DOCUMENTCLASS="") { // no related item Quit "" } Set result = "",tCls=..GetClassName(Name) // This changes with MAK1867 If $$$defClassDefined(tCls),..Exists(Name) { Set:result'="" result=result_"," Set result = result _ tCls _ ".cls" } Quit result } } 
Source: https://habr.com/ru/post/279579/
All Articles