Ticket #266: ReactorAdapter.cfc

File ReactorAdapter.cfc, 19.8 kB (added by dhughes, 19 years ago)
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 i = "" />
71        <cfset var j = "" />
72       
73        <cfif structKeyExists(variables._mdCache, arguments.table)>
74                <cfreturn variables._mdCache[arguments.table] />
75        </cfif>
76
77        <cfset result.primaryKeys = arrayNew(1) />
78        <cfset result.labelField = "" />
79       
80        <!--- Determine the "label" field --->
81        <cfloop from="1" to="#arrayLen(fields)#" index="i">
82                <cfif fields[i].cfdatatype eq "string">
83                        <cfset result.labelField = fields[i].alias>
84                        <cfbreak />
85                </cfif>
86        </cfloop>
87        <cfif not len(result.labelField)>
88                <cfset result.labelField = fields[1].alias />
89        </cfif>
90       
91        <!--- Add simple fields --->
92        <cfloop from="1" to="#arrayLen(fields)#" index="i">
93                <cfset md[fields[i].alias] = duplicate(fields[i]) />
94                <cfset md[fields[i].alias].sourceObject = "" />
95                <cfset md[fields[i].alias].sourceColumn = "" />
96                <cfset md[fields[i].alias].sourceKey = "" />
97                <cfset md[fields[i].alias].relationship = false />
98                <cfset md[fields[i].alias].linkingRelationship = false />
99                <cfset md[fields[i].alias].pluralRelationship = false />
100               
101                <cfif dict.getValue("#arguments.table#.#fields[i].alias#.label") neq "#arguments.table#.#fields[i].alias#.label">
102                        <cfset md[fields[i].alias].label = dict.getValue("#arguments.table#.#fields[i].alias#.label") />
103                <cfelse>
104                        <cfset md[fields[i].alias].label = determineLabel(fields[i].alias) />           
105                </cfif>
106               
107                <cfif md[fields[i].alias].label eq fields[i].alias>
108                        <cfset md[fields[i].alias].label = determineLabel(md[fields[i].alias].label) />
109                </cfif>
110               
111                <cfset md[fields[i].alias].comment = dict.getValue("#arguments.table#.#fields[i].alias#.comment") />
112       
113                <cfif fields[i].primaryKey>
114                        <cfset arrayAppend(result.primaryKeys, fields[i].name) />
115                </cfif>
116                       
117                       
118                <cfset arrayAppend(properties, md[fields[i].alias]) />
119               
120        </cfloop>
121       
122        <!--- Add hasOne --->
123        <cfloop from="1" to="#arrayLen(hasOne)#" index="i">
124                <!--- If this table contains the primary key, add a "virtual" property" --->
125                <cfif md[hasOne[i].relate[1].from].primaryKey>
126                        <cfset field = createEmptyField(rmd) />
127                       
128                        <cfset field.alias = hasOne[i].alias />
129                        <cfset field.relationship = true />
130                        <cfset field.linkingRelationship = false />
131                        <cfset field.pluralRelationship = false />
132                        <cfset determineSource(field, hasOne[i]) />
133                       
134                        <cfset md[field.alias] = field />
135                        <cfset arrayAppend(properties, field) />
136                <!--- Else, replace the physical field with the relationship --->
137                <cfelse>
138                        <!--- Overwrite the physical field this relationship replaces --->
139                        <cfset field = md[hasOne[i].relate[1].from] />
140                        <cfset md[hasOne[i].alias] = md[hasOne[i].relate[1].from] />
141                       
142                        <cfif hasOne[i].alias neq hasOne[i].relate[1].from>
143                                <cfset structDelete(md, hasOne[i].relate[1].from) />
144                        </cfif>
145                       
146                        <!--- Change its alias to the relationship's alias --->
147                        <cfset md[hasOne[i].alias].alias = hasOne[i].alias />
148
149                        <!--- Determine the source --->
150                        <cfset determineSource(md[hasOne[i].alias], hasOne[i]) />
151                        <cfset md[hasOne[i].alias].relationship = true />
152
153                </cfif>
154        </cfloop>
155       
156        <!--- Add direct (no link) hasMany --->
157        <cfloop from="1" to="#arrayLen(hasMany)#" index="i">
158                <!---
159                        Some hasMany's are created as a result of linked
160                        hasMany's - if so, their NAME is the same
161                        as one of the LINK attribs
162                --->
163                <cfset includeThisHasMany = true />
164               
165                <cfloop from="1" to="#arrayLen(hasMany)#" index="j">
166                        <cfif structKeyExists(hasMany[j], "link")
167                                                and hasMany[j].link[1] eq hasMany[i].name>
168                                <cfset includeThisHasMany = false />
169                        </cfif>
170                </cfloop>
171               
172                <cfif includeThisHasMany
173                                        and structKeyExists(hasMany[i], "relate")
174                                        and not structKeyExists(hasMany[i], "link")>
175                        <cfset field = createEmptyField(rmd) />
176                       
177                        <cfset field.alias = hasMany[i].alias />
178                        <cfset field.relationship = true />
179                        <cfset field.linkingRelationship = false />
180                        <cfset field.pluralRelationship = true />
181                       
182                        <cfset determineSource(field, hasMany[i]) />
183
184                        <!---
185                                Non-linked hasManys are a special case where we need to know
186                                the foreign key in the source table to set to NULL when
187                                relationships are deleted
188                        --->
189                        <cfset field.sourceTableForeignKey = hasMany[i].relate[1].to />
190                       
191                        <cfset md[field.alias] = field />
192                        <cfset arrayAppend(properties, field) />
193                </cfif>
194        </cfloop>
195
196        <!--- Add linked hasMany --->
197        <cfloop from="1" to="#arrayLen(hasMany)#" index="i">
198                <cfif structKeyExists(hasMany[i], "link")
199                                        and not structKeyExists(hasMany[i], "relate")>
200                        <cfset field = createEmptyField(rmd) />
201                       
202                        <cfset field.alias = hasMany[i].alias />
203                        <cfset field.relationship = true />
204                        <cfset field.linkingRelationship = true />
205                        <cfset field.pluralRelationship = true />
206                        <cfset field.name = hasMany[i].link[1] />
207                        <cfset determineSource(field, hasMany[i]) />
208
209                        <cfset md[field.alias] = field />
210                        <cfset arrayAppend(properties, field) />
211                </cfif>
212        </cfloop>
213
214        <cfset label = dict.getValue("#arguments.table#.label") />
215       
216        <cfif label eq "#arguments.table#.label">
217                <cfset label = determineLabel(arguments.table) />
218        </cfif>
219       
220        <cfset result.label = label />
221        <cfset result.alias = rmd.getAlias() />
222       
223        <cfxml variable="result.xml">
224                <object>
225                        <alias>#rmd.getAlias()#</alias>
226                        <label>#label#</label>
227                        <labelfield>#result.labelfield#</labelfield>
228                        <properties>
229                        <cfloop from="1" to="#arrayLen(properties)#" index="i">
230                                <property>
231                                        <cfloop list="nullable,cfdatatype,primarykey,sourcecolumn,pluralrelationship,relationship,sourceobject,name,default,sourcekey,length,alias,label,comment" index="j">
232                                                <#j#><![CDATA[#properties[i][j]#]]></#j#>
233                                        </cfloop>
234                                </property>                                                     
235                        </cfloop>
236                        </properties>
237                </object>
238        </cfxml>
239       
240        <cfset result.properties = md />
241       
242        <cfset variables._mdCache[arguments.table] = result />
243
244        <cfreturn result />
245</cffunction>
246
247<cffunction name="getCriteriaProperties" returntype="string" output="false" access="public">
248        <cfargument name="table" type="string" required="true" />
249       
250        <cfset var result = "" />
251        <cfset var md = "" />
252        <cfset var i = "" />
253       
254        <cfif not structKeyExists(variables._cpCache, arguments.table)>
255                <cfset md = getObjectFields(arguments.table) />
256               
257                <cfset variables._cpCache[arguments.table] = result />         
258        <cfelse>
259                <cfset result = variables._cpCache[arguments.table] />
260        </cfif>
261       
262        <cfreturn getObjectFields(arguments.table) />
263</cffunction>
264
265<cffunction name="determineSource" returntype="void" output="false" access="private">
266        <cfargument name="field" type="struct" required="true" />
267        <cfargument name="relationship" type="struct" required="true" />
268       
269        <cfset var rmd = getReactor().createMetadata(arguments.relationship.name) />
270        <cfset var dict = getReactor().createDictionary(arguments.relationship.name) />
271        <cfset var fields = rmd.getfields() />
272        <cfset var i = "" />
273
274        <cfif not arrayLen(fields)>
275                <cfthrow type="reactorAdapter.determineSource.noFields" message="The source table (#arguments.relationship.name#) has no columns." />
276        </cfif>
277       
278        <cfset arguments.field.sourceObject = arguments.relationship.name />
279        <cfset arguments.field.sourceColumn = fields[1].name />
280       
281        <cfloop from="1" to="#arrayLen(fields)#" index="i">
282                <cfif fields[i].primaryKey>
283                        <cfset arguments.field.sourceKey = fields[i].name />
284                </cfif>
285        </cfloop>
286
287        <cfloop from="1" to="#arrayLen(fields)#" index="i">
288                <cfif fields[i].cfDataType eq "string"
289                                        and right(fields[i].name, 2) neq "id"
290                                        and fields[i].length lt 65535>
291                        <cfset arguments.field.sourceColumn = fields[i].name />
292                        <cfbreak />
293                </cfif>
294        </cfloop>
295
296        <cfset arguments.field.label = determineLabel(arguments.field.alias) />
297               
298        <cfset arguments.field.comment = dict.getValue("#arguments.relationship.name#.#arguments.field.sourceColumn#.comment") />
299</cffunction>
300
301<cffunction name="determineLabel" returntype="string" output="false" access="private">
302        <cfargument name="label" type="string" required="true" />
303       
304        <cfset var i = "" />
305        <cfset var char = "" />
306        <cfset var result = "" />
307       
308        <cfloop from="1" to="#len(arguments.label)#" index="i">
309                <cfset char = mid(arguments.label, i, 1) />
310               
311                <cfif i eq 1>
312                        <cfset result = result & ucase(char) />
313                <cfelseif asc(lCase(char)) neq asc(char)>
314                        <cfset result = result & " " & ucase(char) />
315                <cfelse>
316                        <cfset result = result & char />
317                </cfif>
318        </cfloop>
319
320        <cfreturn result />     
321</cffunction>
322
323<cffunction name="createEmptyField" returntype="struct" output="false" access="private">
324        <cfargument name="metadata" required="true" />
325
326        <cfset var field = structNew() />
327        <cfset field.relationship = false />
328        <cfset field.linkingRelationship  = false />
329        <cfset field.pluralRelationship  = false />
330        <cfset field.sourceTableForeignKey = "" />
331        <cfset field.sourceKey = "" />
332        <cfset field.sourceColumn = "" />
333        <cfset field.sourceObject = "" />
334        <cfset field.alias = "" />
335        <cfset field.cfDataType = "" />
336        <cfset field.cfSqlType = "" />
337        <cfset field.dbDataType = "" />
338        <cfset field.default = "" />
339        <cfset field.identity = false />
340        <cfset field.length = 0 />
341        <cfset field.name = "" />
342        <cfset field.nullable = false />
343        <cfset field.object = arguments.metadata.getAlias() />
344        <cfset field.primaryKey = false />
345        <cfset field.sequence = "" />
346        <cfset field.label = "" />
347        <cfset field.comment = "" />
348        <cfset field.link = false />
349       
350        <cfreturn field />
351</cffunction>
352
353<cffunction name="list" returntype="any" output="false" access="public">
354        <cfargument name="table" type="string" required="true" />
355        <cfargument name="criteria" type="struct" required="false" />
356        <cfargument name="orderColumn" type="string" required="false" />
357        <cfargument name="orderAscending" type="boolean" required="false" default="true" />
358        <cfargument name="gatewayMethod" type="string" required="false" />
359        <cfargument name="gatewayBean" type="string" required="false" />
360
361        <cfset var gw = getReactor().createGateway(arguments.table) />
362        <cfset var field = "" />
363        <cfset var result = "" />
364        <cfset var query = gw.createQuery() />
365        <cfset var where = query.getWhere() />
366        <cfset var order = query.getOrder() />
367       
368        <cfif not structKeyExists(arguments, "gatewayMethod")>
369                <cfloop collection="#arguments.criteria#" item="field">
370                                <cfset where.isEqual(arguments.table, field, arguments.criteria[field]) />
371                </cfloop>
372               
373                <cfif structKeyExists(arguments, "orderColumn")>
374                        <cfif arguments.orderAscending>
375                                <cfset order.setAsc(arguments.table, arguments.orderColumn) />
376                        <cfelse>
377                                <cfset order.setDesc(arguments.table, arguments.orderColumn) />
378                        </cfif>
379                </cfif>
380               
381                <cfset result = gw.getByQuery(query) />
382        <cfelse>
383                <cfinvoke component="#gw#" method="#arguments.gatewaymethod#" argumentcollection="#criteria#" returnvariable="result" />
384        </cfif>
385       
386        <cfreturn result />
387</cffunction>
388
389<cffunction name="new" returntype="any" output="false" access="public">
390        <cfargument name="table" type="string" required="true" />
391        <cfreturn getReactor().createRecord(arguments.table) />
392</cffunction>
393
394<cffunction name="read" returntype="any" output="false" access="public">
395        <cfargument name="table" type="string" required="true" />
396        <cfargument name="primaryKeys" type="struct" required="true" />
397       
398        <cfset var i = "" />
399        <cfset var record = new(arguments.table) />     
400
401        <cfloop collection="#primaryKeys#" item="i">
402                <cfinvoke component="#record#" method="set#i#">
403                        <cfinvokeargument name="#i#" value="#primaryKeys[i]#" />
404                </cfinvoke>
405        </cfloop>
406
407        <cfset record.load() />
408       
409        <cfreturn record />
410</cffunction>
411
412<cffunction name="validate" returntype="any" output="false" access="public">
413        <cfargument name="table" type="string" required="true" />
414        <cfargument name="record" type="any" required="true" />
415       
416        <cfset var errors = "" />
417        <cfset var dict = "" />
418        <cfset var errorCollection = createObject("component", "ModelGlue.Util.ValidationErrorCollection").init() />
419       
420        <cfset arguments.record.validate() />
421       
422        <cfif arguments.record.hasErrors()>     
423                <cfset errors = arguments.record._getErrorCollection().getErrors() />
424                <cfset dict = arguments.record._getDictionary() />
425               
426                <cfloop from="1" to="#arrayLen(errors)#" index="i">
427                        <cfset errorCollection.addError(listGetAt(errors[i], 2, "."), dict.getValue(errors[i])) />
428                </cfloop>
429        </cfif>
430               
431        <cfreturn errorCollection />
432</cffunction>
433
434<cffunction name="assemble" returntype="void" output="false" access="public">
435        <cfargument name="event" type="ModelGlue.unity.eventrequest.EventContext" required="true" />
436        <cfargument name="target" type="any" required="true" />
437
438        <cfset var record = arguments.target />
439        <cfset var table = arguments.event.getArgument("object") />
440        <cfset var objectName = listLast(table, ".") />
441        <cfset var metadata = getObjectMetadata(table) />
442        <cfset var property = "" />
443        <cfset var targetObject = "" />
444        <cfset var criteria = "" />
445        <cfset var newValue = "" />
446        <cfset var sourceObject = "" />
447        <cfset var currentChildren = "" />
448        <cfset var selectedChildId = "" />
449        <cfset var selectedChildIds = "" />
450        <cfset var currentChild = "" />
451        <cfset var currentChildId = "" />
452        <cfset var currentChildIds = "" />
453        <cfset var testedChildId = "" />
454        <cfset var childRecord = "" />
455        <cfset var i = "" />
456        <cfset var j = "" />
457        <cfset var tmp = "" />
458        <cfset var deletionQueue = arrayNew(1) />
459       
460        <!--- Update all direct properties --->
461        <cfif arguments.event.argumentExists("properties")>
462                <cfset arguments.event.makeEventBean(record, arguments.event.getArgument("properties", "")) />
463        <cfelse>
464                <cfset arguments.event.makeEventBean(record) />
465        </cfif>
466               
467        <!--- Manage plural relationship properties --->
468        <cfloop collection="#metadata.properties#" item="i">
469                <cfset deletionQueue = arrayNew(1) />
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                               
489                                <cfif not listFindNoCase(selectedChildIds, currentChildId)>
490                                       
491                                        <!--- If it's a linking relationship, we want to remove the link:  queue criteria --->
492                                        <cfif metadata.properties[i].linkingRelationship>
493                                                <cfset criteria = structNew() />
494                                                <cfset criteria[metadata.properties[i].sourceKey] = currentChildId />
495                                                <cfset arrayAppend(deletionQueue, criteria) />
496                                        <!--- Otherwise, we null the foreign key field in the target object --->
497                                        <cfelse>
498                                                <cfinvoke component="#childRecord#" method="set#metadata.properties[i].sourceTableForeignKey#">
499                                                        <cfinvokeargument name="#metadata.properties[i].sourceTableForeignKey#" value="" />
500                                                </cfinvoke>
501                                                <cfset orm.commit(table, record) />
502                                        </cfif>
503                                </cfif>
504                        </cfloop>
505
506                        <cfloop from="1" to="#arrayLen(deletionQueue)#" index="j">
507                                <cfset currentChildren.delete(argumentCollection=deletionQueue[j], useTransaction=false) />
508                        </cfloop>
509                               
510                        <!--- Add any selected children to the currentChildren, adding any new children --->
511                        <cfloop list="#selectedChildIds#" index="selectedChildId">
512                                <cfif not listFindNoCase(currentChildIds, selectedChildId)>
513                                        <cfset criteria = structNew() />
514                                        <cfset criteria[metadata.properties[i].sourceKey] = selectedChildId />
515                                        <cfset childRecord = currentChildren.add(argumentCollection=criteria) />
516                                </cfif>
517                        </cfloop>
518                </cfif>
519        </cfloop>       
520
521</cffunction>
522
523<cffunction name="commit" returntype="any" output="false" access="public">
524        <cfargument name="table" type="string" required="true" />
525        <cfargument name="record" type="any" required="true" />
526        <cfargument name="useTransaction" type="any" required="false" default="true" />
527       
528        <cfset record.save(arguments.useTransaction) />
529</cffunction>
530
531<cffunction name="delete" returntype="any" output="false" access="public">
532        <cfargument name="table" type="string" required="true" />
533        <cfargument name="primaryKeys" type="struct" required="true" />
534        <cfset var record = read(arguments.table, arguments.primaryKeys) />
535        <cfset record.delete() />
536</cffunction>
537
538</cfcomponent>