Ticket #334: myFusebox.cfc

File myFusebox.cfc, 20.3 kB (added by jjtischer, 4 months ago)
Line 
1<!---
2Copyright 2006-2007 TeraTech, Inc. http://teratech.com/
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15--->
16<cfcomponent hint="I provide the per-request myFusebox data structure and some convenience methods.">
17        <cfscript>
18        this.version.runtime     = "5.5.0";
19        // this.version.runtime     = "5.5.0.#REReplace('$LastChangedRevision: 663 $','[^0-9]','','all')#";
20         
21        this.version.loader      = "unknown";
22        this.version.transformer = "unknown";
23        this.version.parser      = "unknown";
24         
25        this.thisCircuit = "";
26        this.thisFuseaction =  "";
27        this.thisPlugin = "";
28        this.thisPhase = "";
29        this.plugins = structNew();
30        this.parameters = structNew();
31       
32        // the basic default is development-full-load mode:
33        this.parameters.load = true;
34        this.parameters.parse = true;
35        this.parameters.execute = true;
36        // FB5: new execution parameters:
37        this.parameters.clean = false;          // don't delete parsed files by default
38        this.parameters.parseall = false;       // don't compile all fuseactions by default
39         
40        this.parameters.userProvidedLoadParameter = false;
41        this.parameters.userProvidedCleanParameter = false;
42        this.parameters.userProvidedParseParameter = false;
43        this.parameters.userProvidedParseAllParameter = false;
44        this.parameters.userProvidedExecuteParameter = false;
45       
46        // stack frame for do/include parameters:
47        this.stack = structNew();
48       
49        // FB55: ability to turn debug output off per-request:
50        this.showDebug = true;
51        </cfscript>
52       
53        <cffunction name="init" returntype="myFusebox" access="public" output="false"
54                                hint="I am the constructor.">
55                <cfargument name="appKey" type="string" required="true"
56                                        hint="I am FUSEBOX_APPLICATION_KEY." />
57                <cfargument name="attributes" type="struct" required="true"
58                                        hint="I am the attributes (URL and form variables) structure." />
59                <cfargument name="topLevelVariablesScope" type="any" required="true"
60                                        hint="I am the top-level variables scope." />
61               
62                <cfset var theFusebox = structNew() />
63                <cfset var urlParam = "" />
64                <cfset var urlLastArg = "" />
65                <cfset var urlIsArg = true />
66               
67                <cfset variables.variablesScope = arguments.topLevelVariablesScope />
68               
69                <cfset variables.created = getTickCount() />
70                <cfset variables.log = arrayNew(1) />
71                <cfset variables.occurrence = structNew() />
72
73                <cfset variables.appKey = arguments.appKey />
74                <cfset variables.attributes = arguments.attributes />
75               
76                <!--- FB5: indicates whether application was started on this request --->
77                <cfset this.applicationStart = false />
78
79                <!--- we can't guarantee the fusebox exists in application scope yet... --->
80                <cfif structKeyExists(application,variables.appKey)>
81                        <cfset theFusebox = application[variables.appKey] />
82                </cfif>
83               
84                <!--- default myFusebox.parameters depending on "mode" of the application set in fusebox.xml --->
85                <cfif structKeyExists(theFusebox,"mode")>
86                        <cfswitch expression="#theFusebox.mode#">
87                        <!--- FB41 backward compatibility - now deprecated --->
88                        <cfcase value="development">
89                                <cfif structKeyExists(theFusebox,"strictMode") and theFusebox.strictMode>
90                                        <!--- since we don't load fusebox.xml if we throw an exception, we must fixup the value for the next run --->
91                                        <cfset theFusebox.mode = "development-full-load" />
92                                        <cfthrow type="fusebox.badGrammar.deprecated"
93                                                        message="Deprecated feature"
94                                                        detail="'development' is a deprecated execution mode - use 'development-full-load' instead." />
95                                </cfif>
96                                <cfset this.parameters.load = true />
97                                <cfset this.parameters.parse = true />
98                                <cfset this.parameters.execute = true />
99                        </cfcase>
100                        <!--- FB5: replacement for old development mode --->
101                        <cfcase value="development-full-load">
102                                <cfset this.parameters.load = true />
103                                <cfset this.parameters.parse = true />
104                                <cfset this.parameters.execute = true />
105                        </cfcase>
106                        <!--- FB5: new option - does not load fusebox.xml and therefore does not (re-)load fuseboxApplication object --->
107                        <cfcase value="development-circuit-load">
108                                <cfset this.parameters.load = false />
109                                <cfset this.parameters.parse = true />
110                                <cfset this.parameters.execute = true />
111                        </cfcase>
112                        <cfcase value="production">
113                                <cfset this.parameters.load = false />
114                                <cfset this.parameters.parse = false />
115                                <cfset this.parameters.execute = true />
116                        </cfcase>
117                        <cfdefaultcase>
118                                <!--- since we don't load fusebox.xml if we throw an exception, we must fixup the value for the next run --->
119                                <cfset theFusebox.mode = "development-full-load" />
120                                <cfthrow type="fusebox.badGrammar.invalidParameterValue"
121                                                message="Parameter has invalid value"
122                                                detail="The parameter 'mode' must be one of 'development-full-load', 'development-circuit-load' or 'production' in the fusebox.xml file." />
123                        </cfdefaultcase>
124                        </cfswitch>
125                </cfif>
126               
127                <!--- handle SES URLs if appropriate --->
128                <cfif structKeyExists(theFusebox,"queryStringStart") and theFusebox.queryStringStart is not "?">
129                        <!--- looks like SES URL generation is enabled, process CGI.PATH_INFO (we add &= to catch improperly formed URLs) --->
130                       
131                         <!--- If the url contains parameters that are blank the params get jumbled
132                                i.e. ?blankvar=&boolean=1&hello=world
133                                gets setup as
134                                blankvar=boolean
135                                1=hello
136                                Solution: replace =& with a nullstring so we can actually make the url variable blank
137                          --->
138                    <cfset tmpPATH_INFO = replaceNoCase(cgi.PATH_INFO,'=&','=URLNULL&')/> 
139                        <cfloop list="#tmpPATH_INFO#" index="urlParam"
140                                        delimiters="#theFusebox.queryStringStart##theFusebox.queryStringSeparator##theFusebox.queryStringEqual#&=">
141                           
142                           <cfif urlIsArg>
143                              <cfset urlLastArg = urlParam />
144                           <cfelse>
145                                        <cfif urlParam eq "URLNULL">
146                                                <cfset variables.attributes[urlLastArg] = '' />
147                                        <cfelse>
148                                                  <cfset variables.attributes[urlLastArg] = urlParam />
149                                        </cfif>
150                                       
151                           </cfif>
152                           <cfset urlIsArg = not urlIsArg />
153                           
154                           
155                        </cfloop>
156                </cfif>
157                 
158               
159                <!--- did the user pass in any special "fuseboxDOT" parameters for this request? --->
160                <!--- If so, process them --->
161                <!--- note: only if attributes.fusebox.password matches the application password --->
162                <cfif not structKeyExists(variables.attributes,"fusebox.password")>
163                        <cfset variables.attributes["fusebox.password"] = "" />
164                </cfif>
165                <cfif structKeyExists(theFusebox,"password") and
166                                theFusebox.password is variables.attributes['fusebox.password']>
167                        <!--- FB5: does a load and wipes the parsed files out --->
168                        <cfif structKeyExists(variables.attributes,'fusebox.loadclean') and isBoolean(variables.attributes['fusebox.loadclean'])>
169                                <cfset this.parameters.load = variables.attributes['fusebox.loadclean'] />
170                                <cfset this.parameters.clean = variables.attributes['fusebox.loadclean'] />
171                                <cfset this.parameters.userProvidedLoadParameter = true />
172                                <cfset this.parameters.userProvidedCleanParameter = true />
173                        </cfif>
174                        <cfif structKeyExists(variables.attributes,'fusebox.load') and isBoolean(variables.attributes['fusebox.load'])>
175                                <cfset this.parameters.load = variables.attributes['fusebox.load'] />
176                                <cfset this.parameters.userProvidedLoadParameter = true />
177                        </cfif>
178                        <cfif structKeyExists(variables.attributes,'fusebox.parseall') and isBoolean(variables.attributes['fusebox.parseall'])>
179                                <cfset this.parameters.parse = variables.attributes['fusebox.parseall'] />
180                                <cfset this.parameters.parseall = variables.attributes['fusebox.parseall'] />
181                                <cfif this.parameters.parseall>
182                                        <cfset this.parameters.load = true />
183                                </cfif>
184                                <cfset this.parameters.userProvidedParseParameter = true />
185                                <cfset this.parameters.userProvidedParseAllParameter = true />
186                        </cfif>
187                        <cfif structKeyExists(variables.attributes,'fusebox.parse') and isBoolean(variables.attributes['fusebox.parse'])>
188                                <cfset this.parameters.parse = variables.attributes['fusebox.parse'] />
189                                <cfset this.parameters.userProvidedParseParameter = true />
190                        </cfif>
191                        <cfif structKeyExists(variables.attributes,'fusebox.execute') and isBoolean(variables.attributes['fusebox.execute'])>
192                                <cfset this.parameters.execute = variables.attributes['fusebox.execute'] />
193                                <cfset this.parameters.userProvidedExecuteParameter = true />
194                        </cfif>
195                </cfif>
196               
197                <!---
198                        force a load if the runtime and core versions differ: this allows a new
199                        version to be dropped in and the framework will automatically reload!
200                        note: that we must *force* a load, by pretending this is user-provided!
201                --->
202                <cfif structKeyExists(theFusebox,"getVersion") and
203                                isCustomFunction(theFusebox.getVersion)>
204                        <cfif this.version.runtime is not theFusebox.getVersion()>
205                                <cfset this.parameters.userProvidedLoadParameter = true />
206                                <cfset this.parameters.load = true />
207                        </cfif>
208                <cfelse>
209                        <!--- hmm, doesn't look like the core is present (or it's not FB5 Alpha 2 or higher) --->
210                        <cfset this.parameters.userProvidedLoadParameter = true />
211                        <cfset this.parameters.load = true />
212                </cfif>
213
214                <!--- if the fusebox doesn't already exist we definitely want to reload --->
215                <cfif structKeyExists(theFusebox,"isFullyLoaded") and
216                                theFusebox.isFullyLoaded>
217                        <!--- if fully loaded, leave the load parameter alone --->
218                <cfelse>
219                        <cfset this.parameters.load = true />
220                </cfif>
221               
222                <cfreturn this />
223        </cffunction>
224       
225        <cffunction name="getApplication" returntype="any" access="public" output="false"
226                                hint="I am a convenience method to return the fuseboxApplication object without needing to know reference application scope or the FUSEBOX_APPLICATION_KEY variable.">
227       
228                <!---
229                        this is a bit of a hack since we're accessing application scope directly
230                        but it's probably cleaner than exposing a method to allow fuseboxApplication
231                        to inject itself back into myFusebox during compileRequest()...
232                --->
233                <cfreturn application[variables.appKey] />
234       
235        </cffunction>
236       
237        <cffunction name="getApplicationData" returntype="struct" access="public" output="false"
238                                hint="I am a convenience method to return a reference to the application data cache.">
239       
240                <cfreturn getApplication().getApplicationData() />
241       
242        </cffunction>
243       
244        <cffunction name="getCurrentCircuit" returntype="any" access="public" output="false"
245                                hint="I am a convenience method to return the current Fusebox circuit object.">
246       
247                <cfreturn getApplication().circuits[this.thisCircuit] />
248       
249        </cffunction>
250       
251        <cffunction name="getCurrentFuseaction" returntype="any" access="public" output="false"
252                                hint="I am a convenience method to return the current fuseboxAction (fuseaction) object.">
253       
254                <cfreturn getCurrentCircuit().fuseactions[this.thisFuseaction] />
255       
256        </cffunction>
257       
258        <cffunction name="getOriginalCircuit" returntype="any" access="public" output="false"
259                                hint="I am a convenience method to return the original Fusebox circuit object.">
260       
261                <cfreturn getApplication().circuits[this.originalCircuit] />
262       
263        </cffunction>
264       
265        <cffunction name="getOriginalFuseaction" returntype="any" access="public" output="false"
266                                hint="I am a convenience method to return the original fuseboxAction (fuseaction) object.">
267       
268                <cfreturn getCurrentCircuit().fuseactions[this.originalFuseaction] />
269       
270        </cffunction>
271       
272        <cffunction name="getSelf" returntype="string" access="public" output="false"
273                                hint="I return the 'self' string, e.g., index.cfm.">
274
275                <cfif not structKeyExists(variables,"self")>
276                        <cfset variables.self = getApplication().self />
277                </cfif>
278               
279                <cfreturn variables.self />
280
281        </cffunction>   
282       
283        <cffunction name="setSelf" returntype="void" access="public" output="false"
284                                hint="I override the default value of 'self' and I also reset the value of 'myself'.">
285                <cfargument name="self" type="string" required="true"
286                                        hint="I am the new value of 'self', e.g., /myapp/entry.cfm" />
287               
288                <cfset variables.self = arguments.self />
289                <!--- reset myself for consistency with self --->
290                <cfset variables.myself = getApplication().getDefaultMyself(variables.self) />
291               
292        </cffunction>
293
294        <cffunction name="getMyself" returntype="string" access="public" output="false"
295                                hint="I return the 'myself' string, e.g., index.cfm?fuseaction=.">
296
297                <cfif not structKeyExists(variables,"myself")>
298                        <cfset variables.myself = getApplication().myself />
299                </cfif>
300               
301                <cfreturn variables.myself />
302
303        </cffunction>   
304       
305        <cffunction name="setMyself" returntype="void" access="public" output="false"
306                                hint="I override the default value of 'myself'.">
307                <cfargument name="myself" type="string" required="true"
308                                        hint="I am the new value of 'myself'." />
309               
310                <cfset variables.myself = arguments.myself />
311               
312        </cffunction>
313
314        <cffunction name="do" returntype="string" access="public" output="true"
315                                hint="I compile and execute a specific fuseaction.">
316                <cfargument name="action" type="string" required="true"
317                                        hint="I am the full name of the requested fuseaction (circuit.fuseaction)." />
318                <cfargument name="contentVariable" type="string" default=""
319                                        hint="I indicate an attributes / event scope variable in which to store the output." />
320                <cfargument name="returnOutput" type="boolean" default="false"
321                                        hint="I indicate whether to display output (false - default) or return the output (true)." />
322                <cfargument name="append" type="boolean" default="false"
323                                        hint="I indicate whether to append output (false - default) to the content variable." />
324
325                <cfset var c = this.thisCircuit />
326                <cfset var f = this.thisFuseaction />
327                <cfset var output =
328                                getApplication().do(
329                                        arguments.action,
330                                        this,
331                                        arguments.returnOutput or arguments.contentVariable is not "") />
332       
333                <cfset this.thisFuseaction = f />
334                <cfset this.thisCircuit = c />
335                       
336                <cfif arguments.contentVariable is not "">
337                        <!--- ticket #290 - allow append on content variables --->
338                        <cfif structKeyExists(variables.variablesScope,arguments.contentVariable) and arguments.append>
339                                <cfset variables.variablesScope[arguments.contentVariable] = variables.variablesScope[arguments.contentVariable] & output />
340                        <cfelse>
341                                <cfset variables.variablesScope[arguments.contentVariable] = output />
342                        </cfif>
343                </cfif>
344               
345                <cfreturn output />
346               
347        </cffunction>
348       
349        <cffunction name="relocate" returntype="void" access="public" output="true"
350                                hint="I provide the same functionality as the relocate verb.">
351                <cfargument name="url" type="string" required="false" />
352                <cfargument name="xfa" type="string" required="false" />
353                <cfargument name="addtoken" type="boolean" default="false" />
354                <cfargument name="type" type="string" default="client" />
355
356                <cfset var theUrl = "" />
357               
358                <!--- url/xfa - exactly one is required --->
359                <cfif structKeyExists(arguments,"url")>
360                        <cfif structKeyExists(arguments,"xfa")>
361                                <cfthrow type="fusebox.badGrammar.requiredAttributeMissing"
362                                                message="Required attribute is missing"
363                                                detail="Either the attribute 'url' or 'xfa' is required, for a 'relocate' verb in fuseaction #this.thiscircuit#.#this.thisFuseaction#." />
364                        <cfelse>
365                                <cfset theUrl = arguments.url />
366                        </cfif>
367                <cfelseif structKeyExists(arguments,"xfa")>
368                        <cfset theUrl = getMyself() & variables.variablesScope.xfa[arguments.xfa] />
369                <cfelse>
370                        <cfthrow type="fusebox.badGrammar.requiredAttributeMissing"
371                                        message="Required attribute is missing"
372                                        detail="Either the attribute 'url' or 'xfa' is required, for a 'relocate' verb in fuseaction #this.thiscircuit#.#this.thisFuseaction#." />
373                </cfif>
374               
375                <!--- type - server|client|moved - we do not support javascript here --->
376                <cfif arguments.type is "server">
377
378                        <cfset getPageContext().forward(theUrl) />
379
380                <cfelseif arguments.type is "client">
381
382                        <cflocation url="#theUrl#" addtoken="#arguments.addtoken#" />
383
384                <cfelseif arguments.type is "moved">
385
386                        <cfheader statuscode="301" statustext="Moved Permanently" />
387                        <cfheader name="Location" value="#theUrl#" />
388                       
389                <cfelse>
390                        <cfthrow type="fusebox.badGrammar.invalidAttributeValue"
391                                        message="Attribute has invalid value"
392                                        detail="The attribute 'type' must either be ""server"", ""client"" or ""moved"", for a 'relocate' verb in fuseaction #this.thisCircuit#.#this.thisFuseaction#." />
393                </cfif>
394               
395                <cfabort />
396
397        </cffunction>
398       
399        <cffunction name="variables" returntype="any" access="public" output="false" hint="I return the top-level variables scope.">
400       
401                <cfreturn variables.variablesScope />
402       
403        </cffunction>
404       
405        <cffunction name="enterStackFrame" returntype="void" access="public" output="false"
406                                hint="I create a new stack frame (for scoped parameters to do/include).">
407               
408                <cfset var frame = structNew() />
409               
410                <cfset frame.__fuseboxStack = this.stack />
411                <cfset this.stack = frame />
412               
413        </cffunction>
414       
415        <cffunction name="leaveStackFrame" returntype="void" access="public" output="false"
416                                hint="I pop the last stack frame (for scoped parameters to do/include).">
417               
418                <cfset this.stack = this.stack.__fuseboxStack />
419               
420        </cffunction>
421       
422        <cffunction name="trace" returntype="void" access="public" output="false"
423                                hint="I add a line to the execution trace log.">
424                <cfargument name="type" hint="I am the type of trace (Fusebox, Compiler, Runtime are used by the framework)." />
425                <cfargument name="message" hint="I am the message to put in the execution trace." />
426               
427                <cfset addTrace(getTickCount() - variables.created,arguments.type,arguments.message) />
428               
429        </cffunction>
430
431        <cffunction name="addTrace" returntype="void" access="private" output="false"
432                                hint="I add a detailed line to the execution trace log.">
433                <cfargument name="time" hint="I am the time taken to get to this point in the request." />
434                <cfargument name="type" hint="I am the type of trace." />
435                <cfargument name="message" hint="I am the trace message." />
436                <cfargument name="occurrence" default="0" hint="I am a placeholder for part of the struct that is added to the log." />
437               
438                <cfif structKeyExists(variables.occurrence,arguments.message)>
439                        <cfset variables.occurrence[arguments.message] = 1 + variables.occurrence[arguments.message] />
440                <cfelse>
441                        <cfset variables.occurrence[arguments.message] = 1 />
442                </cfif>
443                <cfset arguments.occurrence = variables.occurrence[arguments.message] />
444                <cfset arrayAppend(variables.log,arguments) />
445               
446        </cffunction>
447       
448        <cffunction name="renderTrace" returntype="string" access="public" output="false" hint="I render the trace log as HTML.">
449               
450                <cfset var result = "" />
451                <cfset var i = 0 />
452               
453                <cfif this.showDebug>
454                        <cfsavecontent variable="result">
455                                <style type="text/css">
456                                        .fuseboxdebug {clear:both;padding-top:10px;}
457                                        .fuseboxdebug * {font-family:verdana,sans-serif;}
458                                        .fuseboxdebug h3 {margin:16px 0 16px 0;padding:0;border-bottom:1px solid #CCC;font-size:16px;}
459                                        .fuseboxdebug table th {font-size:11pt;text-align:left;}
460                                        .fuseboxdebug table tr.odd {background:#F9F9F9;}
461                                        .fuseboxdebug table tr.even {background:#FFF;}
462                                        .fuseboxdebug table td {border-bottom:1px solid #CCC;font-size:10pt;text-align:left;vertical-align:top;}
463                                        .fuseboxdebug table td.count {text-align:center;}
464                                </style>
465                                <div class="fuseboxdebug">
466                                        <h3>Fusebox debugging:</h3>
467                                        <table cellpadding="2" cellspacing="0" width="100%">
468                                                <tr>
469                                                        <th>Time</td>
470                                                        <th>Category</td>
471                                                        <th>Message</td>
472                                                        <th>Count</td>
473                                                </tr>
474                                                <cfloop index="i" from="1" to="#arrayLen(variables.log)#">
475                                                        <cfoutput>
476                                                                <cfif i mod 2>
477                                                                        <tr class="odd">
478                                                                <cfelse>
479                                                                        <tr class="even">
480                                                                </cfif>
481                                                                <td>#variables.log[i].time#ms</td>
482                                                                <td>#variables.log[i].type#</td>
483                                                                <td>#variables.log[i].message#</td>
484                                                                <td class="count">#variables.log[i].occurrence#</td>
485                                                        </tr></cfoutput>
486                                                </cfloop>
487                                        </table>
488                                </div>
489                        </cfsavecontent>
490                </cfif>
491               
492                <cfreturn result />
493               
494        </cffunction>
495</cfcomponent>