Skip to content

Instantly share code, notes, and snippets.

@eduard93
Created September 17, 2018 09:24
Show Gist options
  • Save eduard93/84851600b5b94326b91bb8893d6e4094 to your computer and use it in GitHub Desktop.
Save eduard93/84851600b5b94326b91bb8893d6e4094 to your computer and use it in GitHub Desktop.
Ensemble publish subscribe sample
<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25" zv="Cache for Windows (x86-64) 2017.2 (Build 744U)" ts="2018-09-17 12:22:13">
<Class name="Test.Alert">
<Description>
Email notification about workflow tasks</Description>
<Super>%Persistent,Ens.Request</Super>
<TimeChanged>64587,65114.949429</TimeChanged>
<TimeCreated>64261,39066.833339</TimeCreated>
<Property name="to">
<Description>
Where to send email</Description>
<Type>%String</Type>
<Parameter name="MAXLEN" value="500"/>
</Property>
<Property name="topic">
<Description>
Header</Description>
<Type>%String</Type>
<Parameter name="MAXLEN" value="250"/>
</Property>
<Property name="username">
<Description>
Login</Description>
<Type>%String</Type>
<Parameter name="MAXLEN" value="250"/>
</Property>
<Property name="text">
<Description>
Mail body</Description>
<Type>%String</Type>
<Parameter name="MAXLEN" value="250000"/>
</Property>
<Storage name="Default">
<Type>%Library.CacheStorage</Type>
<DataLocation>^Test.AlertD</DataLocation>
<DefaultData>AlertDefaultData</DefaultData>
<IdLocation>^Test.AlertD</IdLocation>
<IndexLocation>^Test.AlertI</IndexLocation>
<StreamLocation>^Test.AlertS</StreamLocation>
<Data name="AlertDefaultData">
<Structure>listnode</Structure>
<Subscript/>
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>to</Value>
</Value>
<Value name="3">
<Value>topic</Value>
</Value>
<Value name="4">
<Value>username</Value>
</Value>
<Value name="5">
<Value>text</Value>
</Value>
</Data>
</Storage>
</Class>
<Class name="Test.EmailAlertOperation">
<Description>
Send system alerts and workflow aperts by email</Description>
<Super>Ens.BusinessOperation</Super>
<TimeChanged>64908,44020.73895</TimeChanged>
<TimeCreated>64261,34393.334735</TimeCreated>
<Parameter name="ALERTDOMAIN">
<Description>
Domain for system alerts</Description>
<Default>SYSTEM</Default>
</Parameter>
<Parameter name="ADAPTER">
<Default>EnsLib.EMail.OutboundAdapter</Default>
</Parameter>
<Property name="SubscriptionBO">
<Description>
Subscription operation</Description>
<Type>%String</Type>
<Required>1</Required>
<Parameter name="MAXLEN" value="250"/>
</Property>
<Parameter name="SETTINGS">
<Default><![CDATA[SubscriptionBO:Basic:selector?context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId}]]></Default>
</Parameter>
<Method name="OnMessage">
<FormalSpec>pRequest:%Library.Persistent,*pResponse:Ens.Response</FormalSpec>
<ReturnType>%Status</ReturnType>
<Implementation><![CDATA[
#dim sc As %Status = $$$OK
set class = pRequest.%ClassName($$$YES)
if class = "Ens.AlertRequest" {
set sc = ..onAlert(pRequest,.pResponse)
} elseif class = "Test.Alert" {
set sc = ..onWorkflowAlert(pRequest,.pResponse)
} else {
set sc = $$$ERROR($$$GeneralError, "Invalid message class: " _ class)
}
quit sc
]]></Implementation>
</Method>
<Method name="onAlert">
<FormalSpec>pRequest:Ens.AlertRequest,*pResponse:Ens.Response</FormalSpec>
<ReturnType>%Status</ReturnType>
<Implementation><![CDATA[
#dim sc As %Status = $$$OK
#dim tMailMessage As %Net.MailMessage = ##class(%Net.MailMessage).%New()
set tMailMessage.Subject = "Production error from: " _ pRequest.SourceConfigName
set tMailMessage.Charset = "UTF-8"
if (pRequest.AlertDestination '= "")
{
#; if the Ens.AlertRequest supplied an AlertDestination, then add it to the list of configured Recipients
set sc = ..Adapter.AddRecipients(tMailMessage, pRequest.AlertDestination)
if $$$ISERR(sc) quit sc
}
set sc = ..addDomainSubscribersToMessage(tMailMessage, ..#ALERTDOMAIN)
quit:$$$ISERR(sc) sc
set sc = tMailMessage.TextData.Write(pRequest.AlertText)
quit:$$$ISERR(sc) sc
quit ..Adapter.SendMail(tMailMessage)
]]></Implementation>
</Method>
<Method name="onWorkflowAlert">
<FormalSpec>pRequest:Test.Alert,*pResponse:Ens.Response</FormalSpec>
<ReturnType>%Status</ReturnType>
<Implementation><![CDATA[
#dim sc As %Status = $$$OK
#dim tMailMessage As %Net.MailMessage = ##class(%Net.MailMessage).%New()
set tMailMessage.Subject = "Task notification: " _ pRequest.topic
set tMailMessage.Charset = "UTF-8"
set tMailMessage.IsHTML = $$$YES
if (pRequest.to '= "") {
#; if the Ens.AlertRequest supplied an AlertDestination, then add it to the list of configured Recipients
set sc = ..Adapter.AddRecipients(tMailMessage, pRequest.to)
if $$$ISERR(sc) quit sc
}
set sc = tMailMessage.TextData.Write(pRequest.text)
quit:$$$ISERR(sc) sc
quit ..Adapter.SendMail(tMailMessage)
]]></Implementation>
</Method>
<Method name="addDomainSubscribersToMessage">
<FormalSpec>message:%Net.MailMessage,domain:%String,topic:%String=""</FormalSpec>
<Implementation><![CDATA[
set subRequest = ##class(EnsLib.PubSub.Request).%New()
set subRequest.Topic = topic
set subRequest.DomainName = domain
#dim subResponse As EnsLib.PubSub.Response
set sc = ..SendRequestSync(..SubscriptionBO, subRequest, .subResponse)
set mails = ""
for i=1:1:subResponse.TargetList.Count() {
#dim target As EnsLib.PubSub.Target
set target = subResponse.TargetList.GetAt(i)
set mails = mails _ target.Address _ ","
}
quit ..Adapter.AddRecipients(message, mails)
]]></Implementation>
</Method>
</Class>
<Class name="Test.EmailService">
<Super>Ens.BusinessService</Super>
<TimeChanged>64908,44334.522329</TimeChanged>
<TimeCreated>64266,52300.987228</TimeCreated>
<Parameter name="ADAPTER">
<Default>Ens.InboundAdapter</Default>
</Parameter>
<Property name="UnacceptedTime">
<Description>
Time (in hours) while task can be unaccepted</Description>
<Type>%Integer</Type>
<InitialExpression>12</InitialExpression>
</Property>
<Property name="UncompletedTime">
<Description>
Time (in hours) a task can be uncompleted</Description>
<Type>%Integer</Type>
<InitialExpression>24</InitialExpression>
</Property>
<Property name="URL">
<Description>
Base workflow portal uri</Description>
<Type>%String</Type>
<InitialExpression>"http://workflow-portal/csp/workflow/index.csp"</InitialExpression>
</Property>
<Property name="ApprovalDomain">
<Description>
Subscriptions domain for approval tasks</Description>
<Type>%String</Type>
<InitialExpression>"APPROVAL"</InitialExpression>
<Parameter name="MAXLEN" value="250"/>
</Property>
<Property name="SendBO">
<Description>
Send operation (to send email for example)</Description>
<Type>%String</Type>
<Parameter name="MAXLEN" value="250"/>
</Property>
<Property name="SubscriptionBO">
<Description>
Subscription operation</Description>
<Type>%String</Type>
<Required>1</Required>
<Parameter name="MAXLEN" value="250"/>
</Property>
<Parameter name="SETTINGS">
<Default><![CDATA[SubscriptionBO:Basic:selector?context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId},SendBO:Basic:selector?context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId},ApprovalDomain:Basic,URL:Basic,UnacceptedTime:Basic,UncompletedTime:Basic]]></Default>
</Parameter>
<Method name="OnProcessInput">
<FormalSpec>pInput:%RegisteredObject,*pOutput:%RegisteredObject</FormalSpec>
<ReturnType>%Status</ReturnType>
<Implementation><![CDATA[
do ##class(Test.PPGEmail).%KillExtent()
set ts = $zdt($zts,3,1,3)
#dim sc As %Status = $$$OK
set sc = ..generateEmailData()
quit:$$$ISERR(sc) sc
do ..sendEmails()
do ..setTime(ts)
quit sc
]]></Implementation>
</Method>
<Method name="generateEmailData">
<Description>
Populates Test.PPGEmail with "messages" to send</Description>
<ReturnType>%Status</ReturnType>
<Implementation><![CDATA[
#dim sc As %Status = $$$OK
set sc = ..generateWorkflowData()
quit sc
]]></Implementation>
</Method>
<Method name="determineEmails">
<Description>
Get email addresses by domain and topic.</Description>
<FormalSpec>domain:%String,topic:%String</FormalSpec>
<ReturnType>%List</ReturnType>
<Implementation><![CDATA[
set subRequest = ##class(EnsLib.PubSub.Request).%New()
set subRequest.Topic = topic
set subRequest.DomainName = domain
do ..SendRequestSync(..SubscriptionBO, subRequest, .subResponse,, "Get subscribers for domain: " _ domain _ ", topic: " _ topic)
set mails = ""
for i=1:1:subResponse.TargetList.Count() {
#dim target As EnsLib.PubSub.Target
set target = subResponse.TargetList.GetAt(i)
set mails = mails _ $lb(target.Address)
}
return mails
]]></Implementation>
</Method>
<Method name="generateWorkflowData">
<Description>
Populates Test.PPGEmail with workflow "messages" to send</Description>
<Implementation><![CDATA[
#dim sc As %Status = $$$OK
set types = $lb("new", "unassigned", "uncompleted")
for i=1:1:$ll(types) {
set type = $lg(types, i)
set sc = ..generateWorkflowDataByType(type)
quit:$$$ISERR(sc)
}
quit sc
]]></Implementation>
</Method>
<Query name="newWorkflowTasks">
<Type>%SQLQuery</Type>
<FormalSpec>time</FormalSpec>
<SqlQuery><![CDATA[SELECT TaskStatus_RoleName As topic, "%Subject" As text, "%Message" As message
FROM EnsLib_Workflow.TaskResponse
WHERE "%Status" = 'Unassigned' AND TaskStatus_TimeCreated >= :time
ORDER BY TaskStatus_RoleName]]></SqlQuery>
</Query>
<Query name="unassignedWorkflowTasks">
<Type>%SQLQuery</Type>
<FormalSpec>hours</FormalSpec>
<SqlQuery><![CDATA[SELECT TaskStatus_RoleName As topic, "%Subject" As text, "%Message" As message
FROM EnsLib_Workflow.TaskResponse
WHERE "%Status" = 'Unassigned' AND DATEDIFF('hh', TaskStatus_TimeCreated, NOW()) >= :hours
ORDER BY TaskStatus_RoleName]]></SqlQuery>
</Query>
<Query name="uncompletedWorkflowTasks">
<Type>%SQLQuery</Type>
<FormalSpec>time</FormalSpec>
<SqlQuery><![CDATA[SELECT TaskStatus_RoleName As topic, "%Subject" As text, "%Message" As message
FROM EnsLib_Workflow.TaskResponse
WHERE "%Status" = 'Assigned' AND DATEDIFF('hh', TaskStatus_TimeCreated, NOW()) >= :hours
ORDER BY TaskStatus_RoleName]]></SqlQuery>
</Query>
<Method name="generateWorkflowDataByType">
<Description>
Populates Test.PPGEmail with "messages" to send</Description>
<FormalSpec>type:%String(VALUELIST="new,unassigned,uncompleted")</FormalSpec>
<ReturnType>%Status</ReturnType>
<Implementation><![CDATA[
#dim sc As %Status = $$$OK
set prevTopic = ""
if type = "new" {
set query = "newWorkflowTasks"
set arg = ..getTime()
} elseif type = "unassigned" {
set query = "unassignedWorkflowTasks"
set arg = ..UnacceptedTime
} elseif type = "uncompleted" {
set query = "uncompletedWorkflowTasks"
set arg = ..UncompletedTime
}
#dim rs = $classmethod(,query _ "Func", arg)
while rs.%Next() {
set topic = rs.topic _ "." _ type
if prevTopic'=topic {
set emails = ..determineEmails(..ApprovalDomain, topic)
set prevTopic = topic
}
set message = rs.message
set:message'="" message = " - "_ message
set sc = ##class(Test.PPGEmail).add(..ApprovalDomain, topic, emails, rs.text _ message)
quit:$$$ISERR(sc)
}
quit sc
]]></Implementation>
</Method>
<Method name="getTime">
<Description>
Time from which start to get messages</Description>
<ReturnType>%TimeStamp</ReturnType>
<Implementation><![CDATA[ return $g($$$EnsStaticAppData(..%ConfigName, "emailServiceTS"),"2016-10-01 00:00:00.000")
]]></Implementation>
</Method>
<Method name="setTime">
<Description>
Set time from which start to get messages</Description>
<FormalSpec>timestamp:%TimeStamp</FormalSpec>
<Implementation><![CDATA[ set $$$EnsStaticAppData(..%ConfigName, "emailServiceTS") = timestamp
]]></Implementation>
</Method>
<Method name="sendEmails">
<Description>
Generate mails and send to BO</Description>
<Implementation><![CDATA[
&sql(DECLARE C1 CURSOR FOR
SELECT DISTINCT emails
INTO :email
FROM Test.PPGEmail_emails
)
&sql(OPEN C1)
&sql(FETCH C1)
While (SQLCODE = 0) {
set text = ..generateEmailText(email, ..URL)
set msg = ##class(Test.Alert).%New()
set msg.topic = "Your tasks"
set msg.to = email
set msg.text = text
if ..SendBO '="" {
do ..SendRequestAsync(..SendBO, msg ,"Email with wf tasks for: " _ email)
}
&sql(FETCH C1)
}
&sql(CLOSE C1)
]]></Implementation>
</Method>
<Method name="generateEmailText">
<Description>
Generate email text with all info for user
do ##class(Test.EmailService).generateEmailText("[email protected]")</Description>
<ClassMethod>1</ClassMethod>
<FormalSpec>email:%String,url:%String</FormalSpec>
<ReturnType>%String</ReturnType>
<Implementation><![CDATA[
set stream = ##class(%Stream.TmpCharacter).%New()
do stream.WriteLine("Access your tasks at: " _ url _ "<br>")
do stream.WriteLine("You have the foillowing tasks:<br><br>")
do stream.WriteLine()
set prevDomain = ""
set prevTopic = ""
&sql(DECLARE C2 CURSOR FOR
SELECT "domain", topic, text
INTO :domain, :topic, :text
FROM Test.PPGEmail
WHERE FOR SOME %ELEMENT(emails) (%VALUE=:email)
ORDER BY "domain", topic
)
&sql(OPEN C2)
&sql(FETCH C2)
While (SQLCODE = 0) {
if domain '= prevDomain {
do:prevDomain'="" stream.WriteLine("</ul>")
do stream.WriteLine(..writeDomain(domain))
set prevDomain = domain
set prevTopic = ""
}
if topic '= prevTopic {
do:prevTopic'="" stream.WriteLine("</ul>")
do stream.WriteLine(..writeTopic(topic))
set prevTopic = topic
}
do stream.WriteLine(..writeText(text))
&sql(FETCH C2)
}
&sql(CLOSE C2)
do stream.WriteLine()
do stream.Rewind()
set string = stream.Read($$$MaxCacheInt)
return string
]]></Implementation>
</Method>
<Method name="writeDomain">
<ClassMethod>1</ClassMethod>
<FormalSpec>domain:%String</FormalSpec>
<ReturnType>%String</ReturnType>
<Implementation><![CDATA[ return "<b>" _ domain _ "</b><br><br>"
]]></Implementation>
</Method>
<Method name="writeTopic">
<ClassMethod>1</ClassMethod>
<FormalSpec>topic:%String</FormalSpec>
<ReturnType>%String</ReturnType>
<Implementation><![CDATA[
set topic = $replace(topic, ".", ". ")
return "<i>" _ topic _ "</i><br><ul>"
]]></Implementation>
</Method>
<Method name="writeText">
<ClassMethod>1</ClassMethod>
<FormalSpec>text:%String</FormalSpec>
<ReturnType>%String</ReturnType>
<Implementation><![CDATA[ return "<li>" _ text _ "</li>"
]]></Implementation>
</Method>
</Class>
<Class name="Test.PPGEmail">
<Description>
Class to store subscriptions in PPG</Description>
<Super>%Persistent</Super>
<TimeChanged>64587,65337.703625</TimeChanged>
<TimeCreated>64266,61051.898091</TimeCreated>
<Property name="domain">
<Description>
Domain</Description>
<Type>%String</Type>
<Parameter name="MAXLEN" value="100"/>
</Property>
<Property name="topic">
<Description>
Topic</Description>
<Type>%String</Type>
<Required>1</Required>
<Parameter name="MAXLEN" value="1000"/>
</Property>
<Property name="emails">
<Description>
Where to send current subscription. STORAGEDEFAULT and SQLPROJECTION allow SQL access</Description>
<Type>%String</Type>
<Collection>list</Collection>
<Parameter name="SQLPROJECTION" value="table/column"/>
<Parameter name="STORAGEDEFAULT" value="array"/>
</Property>
<UDLText name="T">
<Content><![CDATA[
// Not sure if it improves performance
]]></Content>
</UDLText>
<UDLText name="T">
<Content><![CDATA[
// Index emailsIndex On emails(ELEMENTS);
]]></Content>
</UDLText>
<Property name="text">
<Description>
Text</Description>
<Type>%String</Type>
<Parameter name="MAXLEN"/>
</Property>
<Method name="add">
<Description>
Add subscription
w ##class(Test.PPGEmail).add("APPROVAL", "Contract approval", $lb("[email protected]","[email protected]"), "text")</Description>
<ClassMethod>1</ClassMethod>
<FormalSpec>domain:%String,topic:%String,emails:%List,text:%String</FormalSpec>
<ReturnType>%Status</ReturnType>
<Implementation><![CDATA[
set obj = ..%New()
set obj.domain = domain
set obj.topic = topic
set obj.text = text
if $listvalid(emails) {
for i=1:1:$ll(emails){
do obj.emails.Insert($lg(emails,i))
}
}
return obj.%Save()
]]></Implementation>
</Method>
<Query name="flushTable">
<Type>%Query</Type>
<SqlQuery>SELECT * FROM Test.PPGEmail</SqlQuery>
</Query>
<Storage name="Default">
<Description>
!!! PPG storage</Description>
<Type>%Library.CacheStorage</Type>
<DataLocation>^||Test.PPGEmailD</DataLocation>
<DefaultData>PPGEmailDefaultData</DefaultData>
<IdLocation>^||Test.PPGEmailD</IdLocation>
<IndexLocation>^||Test.PPGEmailI</IndexLocation>
<StreamLocation>^||Test.PPGEmailS</StreamLocation>
<Data name="PPGEmailDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>domain</Value>
</Value>
<Value name="3">
<Value>topic</Value>
</Value>
<Value name="4">
<Value>text</Value>
</Value>
</Data>
<Data name="emails">
<Attribute>emails</Attribute>
<Structure>subnode</Structure>
<Subscript>"emails"</Subscript>
</Data>
</Storage>
</Class>
</Export>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment