Ticket #264: ReactorAdapter.cfc

File ReactorAdapter.cfc, 19.7 kB (added by dhughes, 19 years ago)

This is a working revision. Added method getObjectFields and updated method getCriteriaProperties

Line 
1<cfcomponent displayname="ReactorAdapter" hint="I am a conrete implementation of a Model-Glue ORM adapter." extends="ModelGlue.unity.orm.AbstractORMAdapter">
2
3<cffunction name="init" returntype="ModelGlue.unity.orm.ReactorAdapter" output="true" access="public">
4        <cfargument name="framework" type="any" required="true" />
5
6        <cfset var tmp = "" />
7
8        <!--- Does a reactor configuration exist? --->
9        <cftry>
10                <cfset tmp = arguments.framework.getNativeBean("reactorConfiguration") />
11                <cfcatch>
12                        <cfset arguments.framework.setUseORMAdapter("false", "No reactorConfiguration bean is present.") />
13                </cfcatch>
14        </cftry>
15               
16        <!--- If we're ok to load, and we can find reactor, try loading it --->
17        <cfif isObject(tmp) and fileExists(expandPath("/reactor") & "/reactorFactory.cfc")>     
18                <cftry>
19                        <cfset variables._reactor = arguments.framework.getNativeBean("ormService") />
20                        <cfset arguments.framework.setUseORMAdapter(true, "Loaded ReactorAdapter") />
21                        <cfset arguments.framework.setORMAdapterName("ModelGlue.unity.orm.ReactorAdapter") />
22                        <cfcatch type="reactor.config.InvalidPathToConfig">
23                                <cfset arguments.framework.setUseORMAdapter("false", "No Reactor.xml file found.") />
24                        </cfcatch>
25                        <cfcatch>
26                                <cfif cfcatch.detail contains "Invalid Path To Config">
27                                        <cfset arguments.framework.setUseORMAdapter("false", "The Reactor.xml file specified in ColdSpring.xml cannot be found.") />
28                                <cfelse>
29                                        <cfrethrow />
30                                </cfif>
31                        </cfcatch>
32                </cftry>
33        </cfif>
34       
35        <cfset variables._ormStatus = arguments.framework.getUseORMAdapter() />
36        <cfset variables._mdCache = structNew() />
37        <cfset variables._cpCache = structNew() />
38       
39        <cfreturn this />
40</cffunction>
41
42<cffunction name="getReactor" returntype="reactor.reactorFactory" output="false" access="private">
43       
44        <cfif not structKeyExists(variables, "_reactor")>
45                <cfthrow type="ReactorAdapter.ReactorNotLoaded" message="You're trying to use Reactor to do scaffolds or generic databases, but Reactor isn't available for the following reason: ""#variables._ormStatus.detail#""" />
46        </cfif>
47
48        <cfreturn variables._reactor />
49</cffunction>
50
51<cffunction name="getObjectFields" access="private" output="false" returntype="string" >
52        <cfargument name="table" type="string" required="true" />
53        <cfset var fields = getReactor().createMetadata(arguments.table).getFieldQuery() />
54        <cfreturn valueList(fields.alias) />
55</cffunction>
56
57<cffunction name="getObjectMetadata" returntype="struct" output="true" access="public">
58        <cfargument name="table" type="string" required="true" />
59
60        <cfset var result = structNew() />
61        <cfset var md = structNew() />
62        <cfset var rmd = getReactor().createMetadata(arguments.table) />
63        <cfset var dict = getReactor().createDictionary(arguments.table) />
64        <cfset var properties = arrayNew(1) />
65        <cfset var fields = rmd.getFields() />
66        <cfset var field = "" />
67        <cfset var hasOne = rmd.getObjectMetadata().hasOne />
68        <cfset var hasMany = rmd.getObjectMetadata().hasMany />
69        <cfset var includeThisHasMany = false />
70        <cfset var label = "" />
71        <cfset var i = "" />
72        <cfset var j = "" />
73       
74        <cfif structKeyExists(variables._mdCache, arguments.table)>
75                <cfreturn variables._mdCache[arguments.table] />
76        </cfif>
77
78        <cfset result.primaryKeys = arrayNew(1) />
79        <cfset result.labelField = "" />
80       
81        <!--- Determine the "label" field --->
82        <cfloop from="1" to="#arrayLen(fields)#" index="i">
83                <cfif fields[i].cfdatatype eq "string">
84                        <cfset result.labelField = fields[i].alias>
85                        <cfbreak />
86                </cfif>
87        </cfloop>
88        <cfif not len(result.labelField)>
89                <cfset result.labelField = fields[1].alias />
90        </cfif>
91       
92        <!--- Add simple fields --->
93        <cfloop from="1" to="#arrayLen(fields)#" index="i">
94                <cfset md[fields[i].alias] = duplicate(fields[i]) />
95                <cfset md[fields[i].alias].sourceObject = "" />
96                <cfset md[fields[i].alias].sourceColumn = "" />
97                <cfset md[fields[i].alias].sourceKey = "" />
98                <cfset md[fields[i].alias].relationship = false />
99                <cfset md[fields[i].alias].linkingRelationship = false />
100                <cfset md[fields[i].alias].pluralRelationship = false />
101               
102                <cfif dict.getValue("#arguments.table#.#fields[i].alias#.label") neq "#arguments.table#.#fields[i].alias#.label">
103                        <cfset md[fields[i].alias].label = dict.getValue("#arguments.table#.#fields[i].alias#.label") />
104                <cfelse>
105                        <cfset md[fields[i].alias].label = determineLabel(fields[i].alias) />           
106                </cfif>
107               
108                <cfif md[fields[i].alias].label eq fields[i].alias>
109                        <cfset md[fields[i].alias].label = determineLabel(md[fields[i].alias].label) />
110                </cfif>
111               
112                <cfset md[fields[i].alias].comment = dict.getValue("#arguments.table#.#fields[i].alias#.comment") />
113       
114                <cfif fields[i].primaryKey>
115                        <cfset arrayAppend(result.primaryKeys, fields[i].name) />
116                </cfif>
117                       
118                       
119                <cfset arrayAppend(properties, md[fields[i].alias]) />
120               
121        </cfloop>
122       
123        <!--- Add hasOne --->
124        <cfloop from="1" to="#arrayLen(hasOne)#" index="i">
125                <!--- If this table contains the primary key, add a "virtual" property" --->
126                <cfif md[hasOne[i].relate[1].from].primaryKey>
127                        <cfset field = createEmptyField(rmd) />
128                       
129                        <cfset field.alias = hasOne[i].alias />
130                        <cfset field.relationship = true />
131                        <cfset field.linkingRelationship = false />
132                        <cfset field.pluralRelationship = false />
133                        <cfset determineSource(field, hasOne[i]) />
134                       
135                        <cfset md[field.alias] = field />
136                        <cfset arrayAppend(properties, field) />
137                <!--- Else, replace the physical field with the relationship --->
138                <cfelse>
139                        <!--- Overwrite the physical field this relationship replaces --->
140                        <cfset field = md[hasOne[i].relate[1].from] />
141                        <cfset md[hasOne[i].alias] = md[hasOne[i].relate[1].from] />
142                       
143                        <cfif hasOne[i].alias neq hasOne[i].relate[1].from>
144                                <cfset structDelete(md, hasOne[i].relate[1].from) />
145                        </cfif>
146                       
147                        <!--- Change its alias to the relationship's alias --->
148                        <cfset md[hasOne[i].alias].alias = hasOne[i].alias />
149
150                        <!--- Determine the source --->
151                        <cfset determineSource(md[hasOne[i].alias], hasOne[i]) />
152                        <cfset md[hasOne[i].alias].relationship = true />
153
154                </cfif>
155        </cfloop>
156       
157        <!--- Add direct (no link) hasMany --->
158        <cfloop from="1" to="#arrayLen(hasMany)#" index="i">
159                <!---
160                        Some hasMany's are created as a result of linked
161                        hasMany's - if so, their NAME is the same
162                        as one of the LINK attribs
163                --->
164                <cfset includeThisHasMany = true />
165               
166                <cfloop from="1" to="#arrayLen(hasMany)#" index="j">
167                        <cfif structKeyExists(hasMany[j], "link")
168                                                and hasMany[j].link[1] eq hasMany[i].name>
169                                <cfset includeThisHasMany = false />
170                        </cfif>
171                </cfloop>
172               
173                <cfif includeThisHasMany
174                                        and structKeyExists(hasMany[i], "relate")
175                                        and not structKeyExists(hasMany[i], "link")>
176                        <cfset field = createEmptyField(rmd) />
177                       
178                        <cfset field.alias = hasMany[i].alias />
179                        <cfset field.relationship = true />
180                        <cfset field.linkingRelationship = false />
181                        <cfset field.pluralRelationship = true />
182                       
183                        <cfset determineSource(field, hasMany[i]) />
184
185                        <!---
186                                Non-linked hasManys are a special case where we need to know
187                                the foreign key in the source table to set to NULL when
188                                relationships are deleted
189                        --->
190                        <cfset field.sourceTableForeignKey = hasMany[i].relate[1].to />
191                       
192                        <cfset md[field.alias] = field />
193                        <cfset arrayAppend(properties, field) />
194                </cfif>
195        </cfloop>
196
197        <!--- Add linked hasMany --->
198        <cfloop from="1" to="#arrayLen(hasMany)#" index="i">
199                <cfif structKeyExists(hasMany[i], "link")
200                                        and not structKeyExists(hasMany[i], "relate")>
201                        <cfset field = createEmptyField(rmd) />
202                       
203                        <cfset field.alias = hasMany[i].alias />
204                        <cfset field.relationship = true />
205                        <cfset field.linkingRelationship = true />
206                        <cfset field.pluralRelationship = true />
207                        <cfset field.name = hasMany[i].link[1] />
208                        <cfset determineSource(field, hasMany[i]) />
209
210                        <cfset md[field.alias] = field />
211                        <cfset arrayAppend(properties, field) />
212                </cfif>
213        </cfloop>
214
215        <cfset label = dict.getValue("#arguments.table#.label") />
216       
217        <cfif label eq "#arguments.table#.label">
218                <cfset label = determineLabel(arguments.table) />
219        </cfif>
220       
221        <cfset result.label = label />
222        <cfset result.alias = rmd.getAlias() />
223       
224        <cfxml variable="result.xml">
225                <object>
226                        <alias>#rmd.getAlias()#</alias>
227                        <label>#label#</label>
228                        <labelfield>#result.labelfield#</labelfield>
229                        <properties>
230                        <cfloop from="1" to="#arrayLen(properties)#" index="i">
231                                <property>
232                                        <cfloop list="nullable,cfdatatype,primarykey,sourcecolumn,pluralrelationship,relationship,sourceobject,name,default,sourcekey,length,alias,label,comment" index="j">
233                                                <#j#><![CDATA[#properties[i][j]#]]></#j#>
234                                        </cfloop>
235                                </property>                                                     
236                        </cfloop>
237                        </properties>
238                </object>
239        </cfxml>
240       
241        <cfset result.properties = md />
242       
243        <cfset variables._mdCache[arguments.table] = result />
244
245        <cfreturn result />
246</cffunction>
247
248<cffunction name="getCriteriaProperties" returntype="string" output="false" access="public">
249        <cfargument name="table" type="string" required="true" />
250       
251        <cfset var result = "" />
252        <cfset var md = "" />
253        <cfset var i = "" />
254       
255        <cfif not structKeyExists(variables._cpCache, arguments.table)>
256                <cfset md = getObjectFields(arguments.table) />
257               
258                <cfset variables._cpCache[arguments.table] = result />         
259        <cfelse>
260                <cfset result = variables._cpCache[arguments.table] />
261        </cfif>
262       
263        <cfreturn getObjectFields(arguments.table) />
264</cffunction>
265
266<cffunction name="determineSource" returntype="void" output="false" access="private">
267        <cfargument name="field" type="struct" required="true" />
268        <cfargument name="relationship" type="struct" required="true" />
269       
270        <cfset var rmd = getReactor().createMetadata(arguments.relationship.name) />
271        <cfset var dict = getReactor().createDictionary(arguments.relationship.name) />
272        <cfset var fields = rmd.getfields() />
273        <cfset var i = "" />
274
275        <cfif not arrayLen(fields)>
276                <cfthrow type="reactorAdapter.determineSource.noFields" message="The source table (#arguments.relationship.name#) has no columns." />
277        </cfif>
278       
279        <cfset arguments.field.sourceObject = arguments.relationship.name />
280        <cfset arguments.field.sourceColumn = fields[1].name />
281       
282        <cfloop from="1" to="#arrayLen(fields)#" index="i">
283                <cfif fields[i].primaryKey>
284                        <cfset arguments.field.sourceKey = fields[i].name />
285                </cfif>
286        </cfloop>
287
288        <cfloop from="1" to="#arrayLen(fields)#" index="i">
289                <cfif fields[i].cfDataType eq "string"
290                                        and right(fields[i].name, 2) neq "id"
291                                        and fields[i].length lt 65535>
292                        <cfset arguments.field.sourceColumn = fields[i].name />
293                        <cfbreak />
294                </cfif>
295        </cfloop>
296
297        <cfset arguments.field.label = determineLabel(arguments.field.alias) />
298               
299        <cfset arguments.field.comment = dict.getValue("#arguments.relationship.name#.#arguments.field.sourceColumn#.comment") />
300</cffunction>
301
302<cffunction name="determineLabel" returntype="string" output="false" access="private">
303        <cfargument name="label" type="string" required="true" />
304       
305        <cfset var i = "" />
306        <cfset var char = "" />
307        <cfset var result = "" />
308       
309        <cfloop from="1" to="#len(arguments.label)#" index="i">
310                <cfset char = mid(arguments.label, i, 1) />
311               
312                <cfif i eq 1>
313                        <cfset result = result & ucase(char) />
314                <cfelseif asc(lCase(char)) neq asc(char)>
315                        <cfset result = result & " " & ucase(char) />
316                <cfelse>
317                        <cfset result = result & char />
318                </cfif>
319        </cfloop>
320
321        <cfreturn result />     
322</cffunction>
323
324<cffunction name="createEmptyField" returntype="struct" output="false" access="private">
325        <cfargument name="metadata" required="true" />
326
327        <cfset var field = structNew() />
328        <cfset field.relationship = false />
329        <cfset field.linkingRelationship  = false />
330        <cfset field.pluralRelationship  = false />
331        <cfset field.sourceTableForeignKey = "" />
332        <cfset field.sourceKey = "" />
333        <cfset field.sourceColumn = "" />
334        <cfset field.sourceObject = "" />
335        <cfset field.alias = "" />
336        <cfset field.cfDataType = "" />
337        <cfset field.cfSqlType = "" />
338        <cfset field.dbDataType = "" />
339        <cfset field.default = "" />
340        <cfset field.identity = false />
341        <cfset field.length = 0 />
342        <cfset field.name = "" />
343        <cfset field.nullable = false />
344        <cfset field.object = arguments.metadata.getAlias() />
345        <cfset field.primaryKey = false />
346        <cfset field.sequence = "" />
347        <cfset field.label = "" />
348        <cfset field.comment = "" />
349        <cfset field.link = false />
350       
351        <cfreturn field />
352</cffunction>
353
354<cffunction name="list" returntype="any" output="false" access="public">
355        <cfargument name="table" type="string" required="true" />
356        <cfargument name="criteria" type="struct" required="false" />
357        <cfargument name="orderColumn" type="string" required="false" />
358        <cfargument name="orderAscending" type="boolean" required="false" default="true" />
359        <cfargument name="gatewayMethod" type="string" required="false" />
360        <cfargument name="gatewayBean" type="string" required="false" />
361
362        <cfset var gw = getReactor().createGateway(arguments.table) />
363        <cfset var field = "" />
364        <cfset var result = "" />
365        <cfset var query = gw.createQuery() />
366        <cfset var where = query.getWhere() />
367        <cfset var order = query.getOrder() />
368       
369        <cfif not structKeyExists(arguments, "gatewayMethod")>
370                <cfloop collection="#arguments.criteria#" item="field">
371                                <cfset where.isEqual(arguments.table, field, arguments.criteria[field]) />
372                </cfloop>
373               
374                <cfif structKeyExists(arguments, "orderColumn")>
375                        <cfif arguments.orderAscending>
376                                <cfset order.setAsc(arguments.table, arguments.orderColumn) />
377                        <cfelse>
378                                <cfset order.setDesc(arguments.table, arguments.orderColumn) />
379                        </cfif>
380                </cfif>
381               
382                <cfset result = gw.getByQuery(query) />
383        <cfelse>
384                <cfinvoke component="#gw#" method="#arguments.gatewaymethod#" argumentcollection="#criteria#" returnvariable="result" />
385        </cfif>
386       
387        <cfreturn result />
388</cffunction>
389
390<cffunction name="new" returntype="any" output="false" access="public">
391        <cfargument name="table" type="string" required="true" />
392        <cfreturn getReactor().createRecord(arguments.table) />
393</cffunction>
394
395<cffunction name="read" returntype="any" output="false" access="public">
396        <cfargument name="table" type="string" required="true" />
397        <cfargument name="primaryKeys" type="struct" required="true" />
398       
399        <cfset var i = "" />
400        <cfset var record = new(arguments.table) />     
401
402        <cfloop collection="#primaryKeys#" item="i">
403                <cfinvoke component="#record#" method="set#i#">
404                        <cfinvokeargument name="#i#" value="#primaryKeys[i]#" />
405                </cfinvoke>
406        </cfloop>
407
408        <cfset record.load() />
409       
410        <cfreturn record />
411</cffunction>
412
413<cffunction name="validate" returntype="any" output="false" access="public">
414        <cfargument name="table" type="string" required="true" />
415        <cfargument name="record" type="any" required="true" />
416       
417        <cfset var errors = "" />
418        <cfset var dict = "" />
419        <cfset var errorCollection = createObject("component", "ModelGlue.Util.ValidationErrorCollection").init() />
420       
421        <cfset arguments.record.validate() />
422       
423        <cfif arguments.record.hasErrors()>     
424                <cfset errors = arguments.record._getErrorCollection().getErrors() />
425                <cfset dict = arguments.record._getDictionary() />
426               
427                <cfloop from="1" to="#arrayLen(errors)#" index="i">
428                        <cfset errorCollection.addError(listGetAt(errors[i], 2, "."), dict.getValue(errors[i])) />
429                </cfloop>
430        </cfif>
431               
432        <cfreturn errorCollection />
433</cffunction>
434
435<cffunction name="assemble" returntype="void" output="false" access="public">
436        <cfargument name="event" type="ModelGlue.unity.eventrequest.EventContext" required="true" />
437        <cfargument name="target" type="any" required="true" />
438
439        <cfset var record = arguments.target />
440        <cfset var table = arguments.event.getArgument("object") />
441        <cfset var objectName = listLast(table, ".") />
442        <cfset var metadata = getObjectMetadata(table) />
443        <cfset var property = "" />
444        <cfset var targetObject = "" />
445        <cfset var criteria = "" />
446        <cfset var newValue = "" />
447        <cfset var sourceObject = "" />
448        <cfset var currentChildren = "" />
449        <cfset var selectedChildId = "" />
450        <cfset var selectedChildIds = "" />
451        <cfset var currentChild = "" />
452        <cfset var currentChildId = "" />
453        <cfset var currentChildIds = "" />
454        <cfset var testedChildId = "" />
455        <cfset var childRecord = "" />
456        <cfset var i = "" />
457        <cfset var j = "" />
458        <cfset var tmp = "" />
459        <cfset var deletionQueue = arrayNew(1) />
460       
461        <!--- Update all direct properties --->
462        <cfif arguments.event.argumentExists("properties")>
463                <cfset arguments.event.makeEventBean(record, arguments.event.getArgument("properties", "")) />
464        <cfelse>
465                <cfset arguments.event.makeEventBean(record) />
466        </cfif>
467               
468        <!--- Manage plural relationship properties --->
469        <cfloop collection="#metadata.properties#" item="i">
470                <!--- Only do this if the property is a plural relationship and the form contains the needed value --->
471                <cfif metadata.properties[i].relationship eq true
472                                        and metadata.properties[i].pluralrelationship
473                                        and arguments.event.valueExists("#metadata.properties[i].alias#|#metadata.properties[i].sourceKey#")>
474                       
475                        <!--- Get an iterator of current child records --->
476                        <cfinvoke component="#record#" method="get#metadata.properties[i].alias#Iterator" returnvariable="currentChildren" />
477
478                        <!--- What are the current childIds? --->
479                        <cfset currentChildIds = currentChildren.getValueList(metadata.properties[i].sourceKey) />
480                       
481                        <!--- What children are selected in the form? --->
482                        <cfset selectedChildIds = arguments.event.getValue("#metadata.properties[i].alias#|#metadata.properties[i].sourceKey#") />
483                       
484                        <!--- Loop over the currentChildren deleting any unselected children --->
485                        <cfloop condition="currentChildren.hasMore()">
486                                <cfset childRecord = currentChildren.getNext() />
487                                <cfinvoke component="#childRecord#" method="get#metadata.properties[i].sourceKey#" returnvariable="currentChildId">
488                                <cfif not listFindNoCase(selectedChildIds, currentChildId)>
489                                       
490                                        <!--- If it's a linking relationship, we want to remove the link:  queue criteria --->
491                                        <cfif metadata.properties[i].linkingRelationship>
492                                                <cfset criteria = structNew() />
493                                                <cfset criteria[metadata.properties[i].sourceKey] = currentChildId />
494                                                <cfset arrayAppend(deletionQueue, criteria) />
495                                        <!--- Otherwise, we null the foreign key field in the target object --->
496                                        <cfelse>
497                                                <cfinvoke component="#childRecord#" method="set#metadata.properties[i].sourceTableForeignKey#">
498                                                        <cfinvokeargument name="#metadata.properties[i].sourceTableForeignKey#" value="" />
499                                                </cfinvoke>
500                                                <cfset orm.commit(table, record) />
501                                        </cfif>
502                                </cfif>
503                        </cfloop>
504
505                        <cfloop from="1" to="#arrayLen(deletionQueue)#" index="j">
506                                <cfset currentChildren.delete(argumentCollection=deletionQueue[j]) />
507                        </cfloop>
508                       
509                        <!--- Add any selected children to the currentChildren, adding any new children --->
510                        <cfloop list="#selectedChildIds#" index="selectedChildId">
511                                <cfif not listFindNoCase(currentChildIds, selectedChildId)>
512                                        <cfset criteria = structNew() />
513                                        <cfset criteria[metadata.properties[i].sourceKey] = selectedChildId />
514                                        <cfset childRecord = currentChildren.add(argumentCollection=criteria) />
515                                </cfif>
516                        </cfloop>
517                </cfif>
518        </cfloop>       
519
520</cffunction>
521
522<cffunction name="commit" returntype="any" output="false" access="public">
523        <cfargument name="table" type="string" required="true" />
524        <cfargument name="record" type="any" required="true" />
525       
526        <cfset record.save() />
527</cffunction>
528
529<cffunction name="delete" returntype="any" output="false" access="public">
530        <cfargument name="table" type="string" required="true" />
531        <cfargument name="primaryKeys" type="struct" required="true" />
532        <cfset var record = read(arguments.table, arguments.primaryKeys) />
533        <cfset record.delete() />
534</cffunction>
535
536</cfcomponent>