XData Studio Assist-InterSystemsCachéクラスのXDataブロックの自動補完

この記事は、新しいInterSystems Developer Communityポータルで公開された私の記事の翻訳です。 Studioのさらに別の機能、XDataでXMLドキュメントを作成する際の自動補完のサポートについて説明します。 この記事では、XDataとコードジェネレーターを使用して特定のルールの作成を簡素化することについて、Albert Fuentesが提起したアイデアを発展させます。 ZENアプリケーション、 %Installerマニフェスト、またはRESTブローカーを開発するときに、XDataですでにオートコンプリートが発生している場合があります。 Studio Assistと呼ばれます。 この機能を設定および使用する方法を説明します。




XDataのXMLオートコンプリート



XMLの自動補完を実装するには、いくつかの方法があります。 しかし、それらはすべて何らかの方法で要約され、 %Studio.SASchemaClassクラスを使用することになります。 一部のスキームはクラスではなく単一のファイルとして説明されています。これらのファイルの例は、Caché/ dev / studio / saschemaがインストールされているフォルダーにあります。 たとえば、 %CSP.RESTで使用されるルーティング記述スキーマファイルは次のとおりです 。このクラスはXMLスキーマを定義しますが、UrlMapの解析にのみ使用されます。 形式は非常に単純で、xml名前空間とプレフィックスを記述します。 以下は、属性とその値を持つタグの階層です。

# 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
      
      





ただし、この場合、これはスタジオのアシスタントとしてのみ適していますが、XMLベースのコード生成を追加する必要があります。 %XGENパッケージのクラスがこれに役立ちます。 残念ながら、これらのクラスは将来のバージョンから削除される場合と削除されない場合があるため、使用が推奨されていないとマークされており、必要な場合はインターシステムズにお問い合わせください。 したがって、スキームを説明するために、一連のクラスを作成する必要があります。XMLの各タグに対して、個別のクラスを作成する必要があります。すべてのルールをコンパイルする別のクラスは、新しいルールのスーパークラスになります。 Albertの記事のルールのXML形式をわずかに変更した結果、ルート定義タグが作成されました。このタグには、ルールタグ、さらには任意の数のアクションタグを含めることができます。 以下は、取得する必要があるXMLの例です。



 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> }
      
      





次に、このようなXMLに基づいてコードを生成する必要があります。これにより、ルールの条件がチェックされ、このルールで説明されているアクションが実行されます。



XGENのおかげで、XDataの自動補完だけでなく、それに基づいてコードを生成する機能も得られます。 タグクラスには、特定のタグのコードを生成するいくつかのメソッドがあります。 これらのメソッドは、 %OnGenerateCode%OnBeforeGenerateCode、および%OnAfterGenerateCodeです。



ルートタグ定義のクラス:



 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 } }
      
      





次に、Ruleタグ:



 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 } }
      
      





ここで、ルールを記述するためのテンプレートになり、結果のXMLをコンパイルできるクラスが必要です。



 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 }
      
      





完全なコードはGitHubで表示できます。



単一ファイル



しかし、Studioの可能性はこれで終わりではありません。 以前の記事のいずれかで、独自のファイルタイプを作成する機能について既に説明しました。 この場合、新しい方式のXML形式を作成することができます。これは、同じスキームに従って、XMLのオートコンプリートといくつかのコードへのコンパイルもサポートします。 現在の例では、 開発者コミュニティに関する私の投稿もあります。



ファイル記述クラスコード
 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 } }
      
      





その後、新しいファイルタイプ* .ruleを選択し、テンプレートクラスの子孫として実際に選択されているファイルを選択して、XMLをコンパイルすることが可能になります。



画像






画像


XML編集モードで別のコードが表示される場合、同じクラスが表示されます。 したがって、編集できるのは1つのXMLだけであり、出力では、ルールを実行するための作業コードを準備できます。



アトリエ



スタジオは、Cachéの唯一の公式開発環境ではありません。 これでアトリエができました。 Atelierでそのような機能をサポートするのはどうですか? そのようなサポートはありませんが、いつ表示されるか、将来表示されるかどうかについての情報もありません。 これは、自動補完とネイティブの両方のファイルタイプに適用されます。 しかし、AtelierはそれぞれEclipseプラットフォームで開発されているため、このような機会はInterSystemsだけでなくプラグインとしても追加できます。



All Articles