├── README.md
└── cls
└── SampleUtil
└── DeleteHelper
├── AddHelper.cls
└── OnDeleteSuper.cls
/README.md:
--------------------------------------------------------------------------------
1 | # DeleteHelper
2 | A Super class for adding a code-generator %OnDelete method
3 |
4 | With regard to making sure that referenced instances are deleted when the referrer is deleted –
5 | This is especially important when purging messaged in Ensemble.
6 | The class includes a Code Generator %OnDelete method, which generates code at compile-time that should take care of deleting persistent objects referenced by the instance being deleted.
7 |
8 | What you'd need to do is add this class as another Super class for your class.
9 | There is also an accompanying class that should help with adding this as a Super class for several classes at once (useful for example for classes that were generated by the SOAP Wizard, which might be quite a few).
10 |
11 | See class reference documentation for more details.
12 |
13 | Find in the Developer Community (where you come to learn about InterSystems technology, sharing solutions and staying up-to-date on the latest developments), in this post further details and related discussion.
14 |
15 |
--------------------------------------------------------------------------------
/cls/SampleUtil/DeleteHelper/AddHelper.cls:
--------------------------------------------------------------------------------
1 | /// A supplement class to the
4 | /// See the
10 | /// Arguments:
11 | ///
33 | ///
34 | /// ENSEMBLE>set status = ##class(SampleUtil.DeleteHelper.AddHelper).AddDeleteHelper(1,1,"ITest.Proxy.s0.*.cls")
35 | /// handling class: ITest.Proxy.s0.Address
36 | ///
37 | /// ENSEMBLE>set status = ##class(SampleUtil.DeleteHelper.AddHelper).AddDeleteHelper(1,-1,"ITest.Proxy.s0.*.cls")
38 | /// handling class: ITest.Proxy.s0.Address
39 | /// handling class: ITest.Proxy.s0.Employee
40 | /// handling class: ITest.Proxy.s0.Person
41 | /// handling class: ITest.Proxy.s0.PersonIdentification
42 | ///
43 | /// ENSEMBLE>set status = ##class(SampleUtil.DeleteHelper.AddHelper).AddDeleteHelper(0,-1,"ITest.Proxy.s0.*.cls")
44 | /// handling class: ITest.Proxy.s0.Address
45 | /// saved: 1
46 | ///
47 | /// Compilation started on 01/10/2017 13:24:54 with qualifiers 'ck'
48 | /// Compiling class ITest.Proxy.s0.Address
49 | /// Compiling table ITest_Proxy_s0.Address
50 | /// Compiling routine ITest.Proxy.s0.Address.1
51 | /// Compilation finished successfully in 0.172s.
52 | /// handling class: ITest.Proxy.s0.Employee
53 | /// saved: 1
54 | ///
55 | /// Compilation started on 01/10/2017 13:24:54 with qualifiers 'ck'
56 | /// Compiling 2 classes, using 2 worker jobs
57 | /// Compiling class ITest.Proxy.s0.Person
58 | /// Compiling class ITest.Proxy.s0.Employee
59 | /// Compiling table ITest_Proxy_s0.Person
60 | /// Compiling table ITest_Proxy_s0.Employee
61 | /// Compiling routine ITest.Proxy.s0.Person.1
62 | /// Compiling routine ITest.Proxy.s0.Employee.1
63 | /// Compilation finished successfully in 0.363s.
64 | /// handling class: ITest.Proxy.s0.Person
65 | /// saved: 1
66 | ///
67 | /// Compilation started on 01/10/2017 13:24:55 with qualifiers 'ck'
68 | /// Compiling class ITest.Proxy.s0.Person
69 | /// Compiling table ITest_Proxy_s0.Person
70 | /// Compiling routine ITest.Proxy.s0.Person.1
71 | /// Compilation finished successfully in 0.152s.
72 | /// handling class: ITest.Proxy.s0.PersonIdentification
73 | /// saved: 1
74 | ///
75 | /// Compilation started on 01/10/2017 13:24:55 with qualifiers 'ck'
76 | /// Compiling class ITest.Proxy.s0.PersonIdentification
77 | /// Compiling table ITest_Proxy_s0.PersonIdentification
78 | /// Compiling routine ITest.Proxy.s0.PersonIdentification.1
79 | /// Compilation finished successfully in 0.122s.
80 | ///
81 | ///
82 | ClassMethod AddDeleteHelper(performDryRun As %Boolean = 1, classesNumLimit As %Integer = 0, classSpec As %String = "", makeRightInheritance As %Boolean = 0, removeExistingOnDelete As %Boolean = 0)
83 | {
84 |
85 |
86 | set statement = ##class(%SQL.Statement).%New()
87 |
88 | Set status = statement.%PrepareClassQuery("%Library.RoutineMgr","StudioOpenDialog")
89 |
90 | #dim result As %SQL.StatementResult
91 |
92 | set result=statement.%Execute(/* spec */ classSpec,/*dir */,/* order by*/,/* system files */0,
93 | /* flat */1,/* NotStudio*/,/*Show Generated */ 0)
94 |
95 |
96 | // Counting in order to allow limiting the number of classes
97 | set count=0
98 |
99 | While result.%Next() {
100 |
101 | // Getting the class name, removing the ".cls" extension at the end
102 | Set className = $Piece(result.%Get("Name"),".",1,*-1)
103 |
104 | // The classes that interest us are only Persistent ones
105 | If $ClassMethod(className,"%IsA","%Persistent") {
106 |
107 | // Will ignore classes that are Business Hosts (i.e. Business Service, Business Process or Business Operation)
108 | // As they do not require this handling
109 | If $ClassMethod(className,"%IsA","Ens.Host")||$ClassMethod(className,"%IsA","Ens.Rule.Definition") {
110 | Continue
111 | }
112 |
113 | // Increment the counter
114 | Set count=count+1
115 |
116 | // Check if the classes number has been limited and if yes if we passed the limit, of so stop
117 | If (classesNumLimit'=-1)&&(count>classesNumLimit) {
118 | Quit
119 | }
120 |
121 | // Report we are working on a specific class. This is important for the "Dry Run"
122 | Write "handling class: ",className,!
123 |
124 | // Open the class definition
125 | Set classDef = ##class(%Dictionary.ClassDefinition).%OpenId(className)
126 |
127 | // Check we are not already extending the Delete Helper class
128 | If classDef.Super '[ "SampleUtil.DeleteHelper.OnDeleteSuper" {
129 |
130 | // Add the Delete Helper to the Super classes list
131 | Set classDef.Super = classDef.Super _ ",SampleUtil.DeleteHelper.OnDeleteSuper"
132 |
133 | // Need also to make sure we add an include (%occErrors)
134 | /// as the Delete Helper code accesses a macro with a status error ($$$LoadObjectNotFound)
135 |
136 | // Depending on whether there is already an Include we might or might not need to start with a comma
137 | If classDef.IncludeCode '= "" {
138 | Set BeginInc = ","
139 | } else {
140 | Set BeginInc = ""
141 | }
142 | // Add the %occErrors include
143 | Set classDef.IncludeCode = classDef.IncludeCode _ BeginInc _ "%occErrors"
144 |
145 | // See note in class reference. In some cases (if explicit inheritance was set to left, we'd need to change this right)
146 | // Can consider to change this from being user-parameter-driven to simply check if the inheritance is left
147 | // and if so change to right, or in any case just change to right without checking
148 | // For now opted to have this depend on user input (as to minimize forced undesired "surprise" changes)
149 | If makeRightInheritance {
150 | Set classDef.Inheritance = "right"
151 | }
152 |
153 | // Assuming this was desired by the caller, if the class definition already includes an %OnDelete method
154 | // then it will take precedence over the Delete Helper inherited one. So this allows to remove the existing method
155 | If removeExistingOnDelete {
156 |
157 | // Loop over methods in the class
158 | For i=1:1:classDef.Methods.Count() {
159 | Set methodName = classDef.Methods.GetAt(i)
160 | // If it is an %OnDelete then remove it (and exit the loop)
161 | If methodName = "%OnDelete" {
162 | Do classDef.Methods.RemoveAt(i)
163 | Quit
164 | }
165 | }
166 | }
167 |
168 | // If this is not a Dry Run then go ahead and save (and compile) the class
169 | if 'performDryRun {
170 |
171 | // Save the class definition
172 | Set status = classDef.%Save()
173 |
174 | write "saved: ",status,!
175 |
176 | // Compile the class
177 | Do $system.OBJ.Compile(className,"ck")
178 | }
179 |
180 | }
181 | }
182 | }
183 | Quit $$$OK
184 | }
185 |
186 | }
187 |
188 |
--------------------------------------------------------------------------------
/cls/SampleUtil/DeleteHelper/OnDeleteSuper.cls:
--------------------------------------------------------------------------------
1 | /// A class to help assist in "deep" deleting of an instance, including references to other persistent classes.
2 | ///
13 | /// Property Name As %String(MAXLEN = "", XMLNAME = "Name") [ Required ];
14 | ///
15 | /// Property SSN As %String(MAXLEN = "", XMLNAME = "SSN") [ Required ];
16 | ///
17 | /// Property DOB As %Date(XMLNAME = "DOB");
18 | ///
19 | /// Property Home As ITest.Proxy.s0.Address(XMLNAME = "Home");
20 | ///
21 | /// Property Office As ITest.Proxy.s0.Address(XMLNAME = "Office");
22 | ///
23 | /// Property Spouse As ITest.Proxy.s0.Person(XMLNAME = "Spouse");
24 | ///
27 | ///
%OnDelete(oid) public {
28 | ///
Set status = 1
29 | ///
Set obj = ..%Open(oid,,.status)
30 | ///
If ('status) {
31 | ///
Set errorCode = $System.Status.GetErrorCodes(status)
32 | ///
If errorCode [ 5809 {
33 | ///
Quit 1
34 | ///
} Else {
35 | ///
Quit status
36 | ///
}
37 | ///
}
38 | ///
If $IsObject(obj.Home) {
39 | ///
Set delStatus = obj.Home.%DeleteId(obj.Home.%Id())
40 | ///
If ('delStatus) {
41 | ///
Set errorCode = $System.Status.GetErrorCodes(delStatus)
42 | ///
If errorCode [ 5810 {
43 | ///
} Else {
44 | ///
Set status=$select(+status:delStatus,1:$$AppendStatus^%occSystem(status,delStatus))
45 | ///
}
46 | ///
}
47 | ///
}
48 | ///
If $IsObject(obj.Office) {
49 | ///
Set delStatus = obj.Office.%DeleteId(obj.Office.%Id())
50 | ///
If ('delStatus) {
51 | ///
Set errorCode = $System.Status.GetErrorCodes(delStatus)
52 | ///
If errorCode [ 5810 {
53 | ///
} Else {
54 | ///
Set status=$select(+status:delStatus,1:$$AppendStatus^%occSystem(status,delStatus))
55 | ///
}
56 | ///
}
57 | ///
}
58 | ///
If $IsObject(obj.Spouse) {
59 | ///
Set delStatus = obj.Spouse.%DeleteId(obj.Spouse.%Id())
60 | ///
If ('delStatus) {
61 | ///
Set errorCode = $System.Status.GetErrorCodes(delStatus)
62 | ///
If errorCode [ 5810 {
63 | ///
} Else {
64 | ///
Set status=$select(+status:delStatus,1:$$AppendStatus^%occSystem(status,delStatus))
65 | ///
}
66 | ///
}
67 | ///
}
68 | ///
Quit status }
69 | ///
70 | /// Property GetListByNameResult As list Of ITest.Proxy.s0.PersonIdentification;
78 | ///
If $IsObject(obj.GetListByNameResult) {
79 | ///
Set key=""
80 | ///
Set item = obj.GetListByNameResult.GetNext(.key)
81 | ///
While key'="" {
82 | ///
If $IsObject(item) {
83 | ///
Set delStatus = item.%DeleteId(item.%Id())
84 | ///
If ('delStatus) {
85 | ///
Set errorCode = $System.Status.GetErrorCodes(delStatus)
86 | ///
If errorCode [ 5810 {
87 | ///
} Else {
88 | ///
Set status=$select(+status:delStatus,1:$$AppendStatus^%occSystem(status,delStatus))
89 | ///
}
90 | ///
}
91 | ///
}
92 | ///
Set item = obj.GetListByNameResult.GetNext(.key)
93 | ///
}
94 | ///
95 | /// If this method returns an error then the object will not be deleted.
104 | ///
105 | /// This method was implemented as a generator method and creates the code according to the properties defined in the class
106 | ClassMethod %OnDelete(oid As %ObjectIdentity) As %Status [ CodeMode = objectgenerator, Private, ServerOnly = 1 ]
107 | {
108 |
109 | // a list ($ListBuild format) of "simple" (non-collection) property names we'll want to delete the references of (because they're Persistent)
110 | Set delPropNames = ""
111 |
112 | // a list ($ListBuild format) of collection (List) property names we'll want to delete the references of (because they're Persistent)
113 | Set delCollectionPropNames = ""
114 |
115 | // NOTE! - this sample might not cover all cases of references to be deleted
116 | // (perhaps for example a reference to a Serial class that has a reference to a Persistent one.
117 | /// Also the code uses the "Class Definition" as its source for the properties
118 | /// and not the "Compiled Class" so inherited references will not be covered)
119 |
120 | // Lists and non-lists or arrays need separate handling (since for lists we'll need to iterate its members)
121 |
122 | // A flag whether we have any Persistent reference properties at all, default to 0/false
123 | Set hasDelProps = 0
124 |
125 | #dim prop As %Dictionary.PropertyDefinition
126 |
127 | // Loop over all properties in the class definition
128 | for i=1:1:%class.Properties.Count() {
129 |
130 | Set prop = %class.Properties.GetAt(i)
131 | // grab the property type
132 | set propType=prop.Type
133 |
134 | // grab the property name
135 | set propName = prop.Name
136 |
137 | // grab the "collection-ness" of the property
138 | set propCollection = prop.Collection
139 |
140 | // check if relationship
141 | If (prop.Relationship)&&(prop.Cardinality="many") {
142 | set propCollection="list"
143 | }
144 | // check if relationship and referencing One or Parent
145 | If (prop.Relationship)&&((prop.Cardinality="one")||(prop.Cardinality="parent")) {
146 | Continue
147 | }
148 |
149 |
150 | // if this is a property who's type starts with '%' (e.g. %String) simply iterate to the next property
151 | If $Extract(propType)="%" Continue
152 |
153 |
154 | If $ClassMethod(propType,"%IsA","%Persistent") {
155 | // "Turn on" the flag that we'll be trying to delete some references in this method
156 | Set hasDelProps=1
157 |
158 | // If this is a collection put the property name in the appropriate property list
159 | If (propCollection="list")||(propCollection="array") {
160 | Set delCollectionPropNames = delCollectionPropNames _ $ListBuild(propName)
161 |
162 |
163 | // If it's not a list (but it's still Persistent) put it in the second
164 | } Else {
165 | Set delPropNames = delPropNames _ $ListBuild(propName)
166 | }
167 | }
168 |
169 | }
170 | Do %code.WriteLine(" Set status = $$$OK")
171 | // If the flag is on then we have references to delete
172 | If hasDelProps {
173 |
174 | // Open the object so we can access it's properties
175 |
176 | Do %code.WriteLine(" Set obj = ..%Open(oid,,.status)")
177 | Do %code.WriteLine(" If $$$ISERR(status) {")
178 | Do %code.WriteLine(" Set errorCode = $System.Status.GetErrorCodes(status)")
179 |
180 | // Maybe we simple already deleted this object (perhaps referenced from more than one object)
181 | // If so ignore this error
182 | Do %code.WriteLine(" If errorCode [ $$$LoadObjectNotFound { ")
183 | Do %code.WriteLine(" Quit $$$OK")
184 | Do %code.WriteLine(" } Else {")
185 | Do %code.WriteLine(" Quit status")
186 | Do %code.WriteLine(" }")
187 | Do %code.WriteLine(" }")
188 |
189 | // Loop over the list of "simple" (non-list) properties
190 | for i=1:1:$ListLength(delPropNames) {
191 | // grab the property name from the list
192 | Set propName = $ListGet(delPropNames,i)
193 | // check it actually currently references a valid object
194 | Do %code.WriteLine(" If $IsObject(obj."_propName_") {")
195 | // Delete the object
196 | Do %code.WriteLine(" Set delStatus = obj."_propName_".%DeleteId(obj."_propName_".%Id())")
197 | Do %code.WriteLine(" If $$$ISERR(delStatus) {")
198 | Do %code.WriteLine(" Set errorCode = $System.Status.GetErrorCodes(delStatus)")
199 | // As when opening the oject, maybe we simple already deleted this object (perhaps referenced from more than one object)
200 | // If so ignore this error
201 | Do %code.WriteLine(" If errorCode [ $$$DeleteObjectNotFound { ")
202 | // In this case simply do nothing and continue to next prop
203 |
204 | Do %code.WriteLine(" } Else {")
205 | // If this is a different error return it.
206 | Do %code.WriteLine(" Set status=$$$ADDSC(status,delStatus)")
207 | Do %code.WriteLine(" }")
208 | Do %code.WriteLine(" }")
209 | Do %code.WriteLine(" }")
210 | }
211 |
212 | // Loop over the list of List collection properties
213 | for i=1:1:$ListLength(delCollectionPropNames) {
214 | Set propName = $ListGet(delCollectionPropNames,i)
215 | // if this list prop is indeed an object
216 | Do %code.WriteLine(" If $IsObject(obj."_propName_") {")
217 | // Loop over all members of the list
218 | Do %code.WriteLine(" Set key=""""")
219 | // grab the next reference (and key)
220 | Do %code.WriteLine(" Set item = obj."_propName_".GetNext(.key)")
221 | Do %code.WriteLine(" While key'="""" {")
222 |
223 |
224 | Do %code.WriteLine(" If $IsObject(item) {")
225 |
226 | // delete it
227 | Do %code.WriteLine(" Set delStatus = item.%DeleteId(item.%Id())")
228 | Do %code.WriteLine(" If $$$ISERR(delStatus) {")
229 | Do %code.WriteLine(" Set errorCode = $System.Status.GetErrorCodes(delStatus)")
230 | // As when opening the oject, maybe we simple already deleted this object (perhaps referenced from more than one object)
231 | // If so ignore this error
232 | Do %code.WriteLine(" If errorCode [ $$$DeleteObjectNotFound { ")
233 | // In this case simply do nothing and continue to next prop
234 |
235 | Do %code.WriteLine(" } Else {")
236 | // If this is a different error return it.
237 | Do %code.WriteLine(" Set status=$$$ADDSC(status,delStatus)")
238 | Do %code.WriteLine(" }")
239 |
240 | Do %code.WriteLine(" }")
241 | Do %code.WriteLine(" }")
242 | Do %code.WriteLine(" Set item = obj."_propName_".GetNext(.key)")
243 | Do %code.WriteLine(" }")
244 | Do %code.WriteLine(" }")
245 | }
246 |
247 | }
248 |
249 | Do %code.WriteLine(" Quit status")
250 |
251 |
252 |
253 | Quit $$$OK
254 | }
255 |
256 | }
257 |
258 |
--------------------------------------------------------------------------------