diff --git a/.gitignore b/.gitignore
index c5744476..c835999b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,10 @@
WEB-INF/*
litepost/*
+mxunit/*
.project
settings.xml
-.settings/org.eclipse.core.resources.prefs
\ No newline at end of file
+.settings/org.eclipse.core.resources.prefs
+# Intellij IDEA files
+*.iml
+*.ipr
+*.idea
\ No newline at end of file
diff --git a/README.txt b/README.txt
index 2f877366..19dcf625 100644
--- a/README.txt
+++ b/README.txt
@@ -1,7 +1,8 @@
-This FW/1 directory is a complete web application and expects to live in its own webroot
-if you plan to run the applications within it. To use FW/1 in a separate webroot you can
-either copy the org directory to that webroot or add a mapping for /org/corfield to the
-corfield folder inside the org directory (or a /org mapping to the org directory directly).
+This FW/1 directory is a complete web application and expects to live in its own
+webroot if you plan to run the applications within it. To use FW/1 in a separate
+webroot you can either copy the org directory to that webroot or add a mapping
+for /org/corfield to the corfield folder inside the org directory (or a /org
+mapping to the org directory directly).
Project home: http://fw1.riaforge.org
@@ -9,4 +10,4 @@ Documentation wiki: http://github.com/seancorfield/fw1/wiki
Blog: http://corfield.org/blog/archives.cfm/category/fw1
-Support: http://groups.google.com/group/framework-one/
\ No newline at end of file
+Support: http://groups.google.com/group/framework-one/
diff --git a/docs/fw1.pdf b/docs/fw1.pdf
new file mode 100644
index 00000000..a560f946
Binary files /dev/null and b/docs/fw1.pdf differ
diff --git a/examples/Application.cfc b/examples/Application.cfc
index 9ab828e6..3aaf145f 100644
--- a/examples/Application.cfc
+++ b/examples/Application.cfc
@@ -6,7 +6,8 @@ component extends="org.corfield.framework" {
// FW/1 - configuration:
variables.framework = {
usingSubsystems = true,
- SESOmitIndex = true
+ SESOmitIndex = true,
+ trace = true
};
// pull in bean factory for hello8:
@@ -17,4 +18,4 @@ component extends="org.corfield.framework" {
}
}
-}
\ No newline at end of file
+}
diff --git a/examples/hello8/model/ioc.cfc b/examples/hello8/model/ioc.cfc
index 1ae53be5..f36adc20 100644
--- a/examples/hello8/model/ioc.cfc
+++ b/examples/hello8/model/ioc.cfc
@@ -1,5 +1,5 @@
/*
- Copyright (c) 2010-2011, Sean Corfield
+ Copyright (c) 2010-2012, Sean Corfield
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -23,15 +23,26 @@ component {
variables.beanInfo = { };
variables.beanCache = { };
variables.autoExclude = [ '/WEB-INF', '/Application.cfc' ];
+ variables.listeners = 0;
setupFrameworkDefaults();
return this;
}
// PUBLIC METHODS
+
+ // programmatically register an alias
+ public any function addAlias( string aliasName, string beanName ) {
+ discoverBeans( variables.folders );
+ variables.beanInfo[ aliasName ] = variables.beanInfo[ beanName ];
+ return this;
+ }
+
// programmatically register new beans with the factory (add a singleton name/value pair)
- public void function addBean( string beanName, any beanValue ) {
+ public any function addBean( string beanName, any beanValue ) {
+ discoverBeans( variables.folders );
variables.beanInfo[ beanName ] = { value = beanValue, isSingleton = true };
+ return this;
}
@@ -44,19 +55,21 @@ component {
// programmatically register new beans with the factory (add an actual CFC)
- public void function declareBean( string beanName, string dottedPath, boolean isSingleton = true ) {
+ public any function declareBean( string beanName, string dottedPath, boolean isSingleton = true ) {
+ discoverBeans( variables.folders );
var singleDir = '';
if ( listLen( dottedPath, '.' ) > 1 ) {
var cfc = listLast( dottedPath, '.' );
var dottedPart = left( dottedPath, len( dottedPath ) - len( cfc ) - 1 );
singleDir = singular( listLast( dottedPart, '.' ) );
}
- var cfcPath = replace( expandPath( '/' & replace( dottedPath, '.', '/', 'all' ) & '.cfc' ), '\', '/', 'all' );
+ var cfcPath = replace( expandPath( '/' & replace( dottedPath, '.', '/', 'all' ) & '.cfc' ), chr(92), '/', 'all' );
var metadata = {
name = beanName, qualifier = singleDir, isSingleton = isSingleton,
path = cfcPath, cfc = dottedPath, metadata = cleanMetadata( dottedPath )
};
variables.beanInfo[ beanName ] = metadata;
+ return this;
}
@@ -64,20 +77,7 @@ component {
public any function getBean( string beanName ) {
discoverBeans( variables.folders );
if ( structKeyExists( variables.beanInfo, beanName ) ) {
- var info = variables.beanInfo[ beanName ];
- if ( info.isSingleton ) {
- // cache on the qualified bean name:
- var qualifiedName = beanName;
- if ( structKeyExists( info, 'name' ) && structKeyExists( info, 'qualifier' ) ) {
- qualifiedName = info.name & info.qualifier;
- }
- if ( !structKeyExists( variables.beanCache, qualifiedName ) ) {
- variables.beanCache[ qualifiedName ] = resolveBean( beanName );
- }
- return variables.beanCache[ qualifiedName ];
- } else {
- return resolveBean( beanName );
- }
+ return resolveBean( beanName );
} else if ( structKeyExists( variables, 'parent' ) ) {
return variables.parent.getBean( beanName );
} else {
@@ -87,6 +87,7 @@ component {
// convenience API for metaprogramming perhaps?
public any function getBeanInfo( string beanName = '' ) {
+ discoverBeans( variables.folders );
if ( len( beanName ) ) {
if ( structKeyExists( variables.beanInfo, beanName ) ) {
return variables.beanInfo[ beanName ];
@@ -105,6 +106,7 @@ component {
// return true iff bean is known to be a singleton
public boolean function isSingleton( string beanName ) {
+ discoverBeans( variables.folders );
if ( structKeyExists( variables.beanInfo, beanName ) ) {
return variables.beanInfo[ beanName ].isSingleton;
} else if ( structKeyExists( variables, 'parent' ) ) {
@@ -115,9 +117,13 @@ component {
}
- // given a bean (by name or by value), call the named setters with the specified property values
+ // given a bean (by name, by type or by value), call the named
+ // setters with the specified property values
public any function injectProperties( any bean, struct properties ) {
- if ( !isSimpleValue( bean ) ) bean = getBean( bean );
+ if ( isSimpleValue( bean ) ) {
+ if ( containsBean( bean ) ) bean = getBean( bean );
+ else bean = createObject( 'component', bean );
+ }
for ( var property in properties ) {
var args = { };
args[ property ] = properties[ property ];
@@ -132,26 +138,57 @@ component {
// are responsible for dealing with that logic (it's safe to reload a child but
// if you reload the parent, you must reload *all* child factories to ensure
// things stay consistent!)
- public void function load() {
+ public any function load() {
discoverBeans( variables.folders );
variables.beanCache = { };
for ( var key in variables.beanInfo ) {
if ( variables.beanInfo[ key ].isSingleton ) getBean( key );
}
+ return this;
}
+
+
+ // add a listener for processing after a (re)load of the factory
+ // called with just the factory, should be a plain function
+ public any function onLoad( any listener ) {
+ var head = { next = variables.listeners, listener = listener };
+ variables.listeners = head;
+ return this;
+ }
// set the parent bean factory
- public void function setParent( any parent ) {
+ public any function setParent( any parent ) {
variables.parent = parent;
+ return this;
}
// PRIVATE METHODS
private boolean function beanIsTransient( string singleDir, string dir, string beanName ) {
- return singleDir == 'bean' || structKeyExists( variables.transients, dir );
+ return singleDir == 'bean' || structKeyExists( variables.transients, dir ) || ( structKeyExists( variables.config, "singletonPattern" ) && refindNoCase( variables.config.singletonPattern, beanName ) == 0 );
}
-
+
+
+ private any function cachable( string beanName) {
+ var newObject = false;
+ var info = variables.beanInfo[ beanName ];
+ if ( info.isSingleton ) {
+ // cache on the qualified bean name:
+ var qualifiedName = beanName;
+ if ( structKeyExists( info, 'name' ) && structKeyExists( info, 'qualifier' ) ) {
+ qualifiedName = info.name & info.qualifier;
+ }
+ if ( !structKeyExists( variables.beanCache, qualifiedName ) ) {
+ variables.beanCache[ qualifiedName ] = createObject( 'component', info.cfc );
+ newObject = true;
+ }
+ return { bean = variables.beanCache[ qualifiedName ], newObject = newObject };
+ } else {
+ return { bean = createObject( 'component', info.cfc ), newObject = true };
+ }
+ }
+
private struct function cleanMetadata( string cfc ) {
var baseMetadata = getComponentMetadata( cfc );
@@ -159,8 +196,8 @@ component {
var md = { extends = baseMetadata };
do {
md = md.extends;
- // gather up setters based on metadata:
- var implicitSetters = false;
+ // gather up setters based on metadata:
+ var implicitSetters = false;
// we have implicit setters if: accessors="true" or persistent="true"
if ( structKeyExists( md, 'persistent' ) && isBoolean( md.persistent ) ) {
implicitSetters = md.persistent;
@@ -176,10 +213,6 @@ component {
var property = md.properties[ i ];
if ( implicitSetters ||
structKeyExists( property, 'setter' ) && isBoolean( property.setter ) && property.setter ) {
- if ( !isSingleton( property.name ) ) {
- // ignore properties that we know to be transients...
- continue;
- }
iocMeta.setters[ property.name ] = 'implicit';
}
}
@@ -200,7 +233,7 @@ component {
var m = arrayLen( func.parameters );
for ( var j = 1; j <= m; ++j ) {
var arg = func.parameters[ j ];
- iocMeta.constructor[ arg.name ] = structKeyExists( arg, 'type' ) ? arg.type : 'any';
+ iocMeta.constructor[ arg.name ] = structKeyExists( arg, 'required' ) ? arg.required : false;
}
}
}
@@ -225,7 +258,7 @@ component {
return remaining;
}
} else {
- var webroot = replace( expandPath( '/' ), '\', '/', 'all' );
+ var webroot = replace( expandPath( '/' ), chr(92), '/', 'all' );
if ( path.startsWith( webroot ) ) {
var rootRelativePath = right( path, len( path ) - len( webroot ) );
return replace( left( rootRelativePath, len( rootRelativePath ) - 4 ), '/', '.', 'all' );
@@ -243,16 +276,17 @@ component {
var folderArray = listToArray( folders );
variables.pathMapCache = { };
for ( var f in folderArray ) {
- discoverBeansInFolder( replace( trim( f ), '\', '/', 'all' ) );
+ discoverBeansInFolder( replace( trim( f ), chr(92), '/', 'all' ) );
}
variables.discoveryComplete = true;
}
+ onLoadEvent();
}
private void function discoverBeansInFolder( string mapping ) {
- var folder = replace( expandPath( mapping ), '\', '/', 'all' );
- var webroot = replace( expandPath( '/' ), '\', '/', 'all' );
+ var folder = replace( expandPath( mapping ), chr(92), '/', 'all' );
+ var webroot = replace( expandPath( '/' ), chr(92), '/', 'all' );
if ( mapping.startsWith( webroot ) ) {
// must be an already expanded path!
folder = mapping;
@@ -270,7 +304,7 @@ component {
// find all the CFCs here:
var cfcs = directoryList( folder, variables.config.recurse, 'path', '*.cfc' );
for ( var cfcOSPath in cfcs ) {
- var cfcPath = replace( cfcOSPath, '\', '/', 'all' );
+ var cfcPath = replace( cfcOSPath, chr(92), '/', 'all' );
// watch out for excluded paths:
var excludePath = false;
for ( var pattern in variables.config.exclude ) {
@@ -329,6 +363,19 @@ component {
}
+ private any function forceCache( any bean, string beanName) {
+ var info = variables.beanInfo[ beanName ];
+ if ( info.isSingleton ) {
+ // cache on the qualified bean name:
+ var qualifiedName = beanName;
+ if ( structKeyExists( info, 'name' ) && structKeyExists( info, 'qualifier' ) ) {
+ qualifiedName = info.name & info.qualifier;
+ }
+ variables.beanCache[ qualifiedName ] = bean;
+ }
+ }
+
+
private void function logMissingBean( string beanName, string resolvingBeanName = '' ) {
var sys = createObject( 'java', 'java.lang.System' );
if ( len( resolvingBeanName ) ) {
@@ -350,6 +397,24 @@ component {
logMissingBean( beanName, resolvingBeanName );
}
}
+
+
+ private void function onLoadEvent() {
+ var head = variables.listeners;
+ while ( isStruct( head ) ) {
+ if ( isCustomFunction( head.listener ) ) {
+ head.listener( this );
+ } else if ( isObject( head.listener ) ) {
+ head.listener.onLoad( this );
+ } else if ( isSimpleValue( head.listener ) &&
+ containsBean( head.listener ) ) {
+ getBean( head.listener ).onLoad( this );
+ } else {
+ throw "invalid onLoad listener registered: #head.listener.toString()#";
+ }
+ head = head.next;
+ }
+ }
private any function resolveBean( string beanName ) {
@@ -381,31 +446,39 @@ component {
if ( structKeyExists( variables.beanInfo, beanName ) ) {
var info = variables.beanInfo[ beanName ];
if ( structKeyExists( info, 'cfc' ) ) {
- // use createObject so we have control over initialization:
- bean = createObject( 'component', info.cfc );
- if ( structKeyExists( info.metadata, 'constructor' ) ) {
- var args = { };
- for ( var arg in info.metadata.constructor ) {
- var argBean = resolveBeanCreate( arg, accumulator );
- // this throws a non-intuitive exception unless we step in...
- if ( !structKeyExists( argBean, 'bean' ) ) {
- throw 'bean not found: #arg#; while resolving constructor arguments for #beanName#';
+ var metaBean = cachable( beanName );
+ bean = metaBean.bean;
+ if ( metaBean.newObject ) {
+ if ( structKeyExists( info.metadata, 'constructor' ) ) {
+ var args = { };
+ for ( var arg in info.metadata.constructor ) {
+ var argBean = resolveBeanCreate( arg, accumulator );
+ // this throws a non-intuitive exception unless we step in...
+ if ( structKeyExists( argBean, 'bean' ) ) {
+ args[ arg ] = argBean.bean;
+ } else if ( info.metadata.constructor[ arg ] ) {
+ throw 'bean not found: #arg#; while resolving constructor arguments for #beanName#';
+ }
+ }
+ var __ioc_newBean = evaluate( 'bean.init( argumentCollection = args )' );
+ // if the constructor returns anything, it becomes the bean
+ // this allows for smart constructors that return things other
+ // than the CFC being created, such as implicit factory beans
+ // and automatic singletons etc (rare practices in CFML but...)
+ if ( isDefined( '__ioc_newBean' ) ) {
+ bean = __ioc_newBean;
+ forceCache( bean, beanName );
}
- args[ arg ] = argBean.bean;
}
- var __ioc_newBean = evaluate( 'bean.init( argumentCollection = args )' );
- // if the constructor returns anything, it becomes the bean
- // this allows for smart constructors that return things other
- // than the CFC being created, such as implicit factory beans
- // and automatic singletons etc (rare practices in CFML but...)
- if ( isDefined( '__ioc_newBean' ) ) bean = __ioc_newBean;
- }
- var setterMeta = findSetters( bean, info.metadata );
- setterMeta.bean = bean;
- accumulator.injection[ beanName ] = setterMeta;
- for ( var property in setterMeta.setters ) {
- resolveBeanCreate( property, accumulator );
}
+ if ( !structKeyExists( accumulator.injection, beanName ) ) {
+ var setterMeta = findSetters( bean, info.metadata );
+ setterMeta.bean = bean;
+ accumulator.injection[ beanName ] = setterMeta;
+ for ( var property in setterMeta.setters ) {
+ resolveBeanCreate( property, accumulator );
+ }
+ }
accumulator.bean = bean;
} else if ( structKeyExists( info, 'value' ) ) {
accumulator.bean = info.value;
@@ -432,7 +505,7 @@ component {
variables.config.exclude = [ ];
}
for ( var elem in variables.autoExclude ) {
- arrayAppend( variables.config.exclude, replace( elem, '\', '/', 'all' ) );
+ arrayAppend( variables.config.exclude, replace( elem, chr(92), '/', 'all' ) );
}
// install bean factory constant:
@@ -449,8 +522,8 @@ component {
variables.transients[ transientFolder ] = true;
}
}
-
- variables.config.version = '0.1.6';
+
+ variables.config.version = '0.4.0';
}
@@ -468,4 +541,4 @@ component {
return single;
}
-}
\ No newline at end of file
+}
diff --git a/examples/userManager/Application.cfc b/examples/userManager/Application.cfc
index 2327854f..703e3fd4 100644
--- a/examples/userManager/Application.cfc
+++ b/examples/userManager/Application.cfc
@@ -1,19 +1,18 @@
-
+component extends="org.corfield.framework" {
-
this.mappings["/userManager"] = getDirectoryFromPath(getCurrentTemplatePath());
this.name = 'fw1-userManager';
// FW/1 - configuration:
variables.framework = {
home = "user.default",
- suppressImplicitService = false
+ suppressImplicitService = false,
+ trace = true
};
function setupApplication()
{
setBeanFactory(createObject("component", "model.ObjectFactory").init(expandPath("./assets/config/beans.xml.cfm")));
}
-
-
\ No newline at end of file
+}
diff --git a/org/corfield/framework.cfc b/org/corfield/framework.cfc
index bbf0ad97..28ecb0f5 100644
--- a/org/corfield/framework.cfc
+++ b/org/corfield/framework.cfc
@@ -1,6 +1,6 @@
component {
/*
- Copyright (c) 2009-2011, Sean Corfield, Ryan Cogswell
+ Copyright (c) 2009-2012, Sean Corfield, Ryan Cogswell
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -23,15 +23,24 @@ component {
variables.cgiScriptName = CGI.SCRIPT_NAME;
variables.cgiPathInfo = CGI.PATH_INFO;
}
- request._fw1 = { };
+ request._fw1 = {
+ cgiScriptName = CGI.SCRIPT_NAME,
+ cgiRequestMethod = CGI.REQUEST_METHOD,
+ controllers = [ ],
+ requestDefaultsInitialized = false,
+ services = [ ],
+ trace = [ ]
+ };
// do not rely on these, they are meant to be true magic...
+ variables.magicApplicationSubsystem = '][';
variables.magicApplicationController = '[]';
variables.magicApplicationAction = '__';
variables.magicBaseURL = '-[]-';
public void function abortController() {
request._fw1.abortController = true;
- throw( type="FW1.AbortControllerException", message="abortController() called" );
+ frameworkTrace( 'abortController() called' );
+ throw( type='FW1.AbortControllerException', message='abortController() called' );
}
public boolean function actionSpecifiesSubsystem( string action ) {
@@ -76,7 +85,7 @@ component {
}
}
if ( path == 'useCgiScriptName' ) {
- path = CGI.SCRIPT_NAME;
+ path = request._fw1.cgiScriptName;
if ( variables.framework.SESOmitIndex ) {
path = getDirectoryFromPath( path );
omitIndex = true;
@@ -137,7 +146,7 @@ component {
} else {
initialDelim = '&';
}
- } else if ( structKeyExists( request, 'generateSES' ) && request.generateSES ) {
+ } else if ( structKeyExists( request._fw1, 'generateSES' ) && request._fw1.generateSES ) {
if ( omitIndex ) {
initialDelim = '';
} else {
@@ -233,20 +242,20 @@ component {
var item = getItem( action );
var tuple = { };
- if ( structKeyExists( request, 'controllerExecutionStarted' ) ) {
- raiseException( type="FW1.controllerExecutionStarted", message="Controller '#action#' may not be added at this point.",
- detail="The controller execution phase has already started. Controllers may not be added by other controller methods." );
+ if ( structKeyExists( request._fw1, 'controllerExecutionStarted' ) ) {
+ raiseException( type='FW1.controllerExecutionStarted', message="Controller '#action#' may not be added at this point.",
+ detail='The controller execution phase has already started. Controllers may not be added by other controller methods.' );
}
tuple.controller = getController( section = section, subsystem = subsystem );
tuple.key = subsystem & variables.framework.subsystemDelimiter & section;
+ tuple.subsystem = subsystem;
+ tuple.section = section;
tuple.item = item;
if ( structKeyExists( tuple, 'controller' ) && isObject( tuple.controller ) ) {
- if ( !structKeyExists( request, 'controllers' ) ) {
- request.controllers = [ ];
- }
- arrayAppend( request.controllers, tuple );
+ frameworkTrace( 'queuing controller', subsystem, section, item );
+ arrayAppend( request._fw1.controllers, tuple );
}
}
@@ -329,14 +338,20 @@ component {
}
if ( variables.framework.defaultSubsystem == '' ) {
- raiseException( type="FW1.subsystemNotSpecified", message="No subsystem specified and no default configured.",
- detail="When using subsystems, every request should specify a subsystem or variables.framework.defaultSubsystem should be configured." );
+ raiseException( type='FW1.subsystemNotSpecified', message='No subsystem specified and no default configured.',
+ detail='When using subsystems, every request should specify a subsystem or variables.framework.defaultSubsystem should be configured.' );
}
return variables.framework.defaultSubsystem;
}
+ /*
+ * override this to provide your environment selector
+ */
+ public string function getEnvironment() {
+ return '';
+ }
/*
* return an action with all applicable parts (subsystem, section, and item) specified
@@ -349,7 +364,13 @@ component {
return getSectionAndItem( action );
}
-
+
+ /*
+ * return the local hostname of the server
+ */
+ public string function getHostname() {
+ return createObject( 'java', 'java.net.InetAddress' ).getLocalHost().getHostName();
+ }
/*
* return the item part of the action
@@ -436,6 +457,13 @@ component {
}
return getDefaultSubsystem();
}
+
+ /*
+ * return the base directory for the current request's subsystem
+ */
+ public string function getSubsystemBase() {
+ return request.subsystemBase;
+ }
/*
* return the (optional) configuration for a subsystem
@@ -510,6 +538,7 @@ component {
*/
public string function layout( string path, string body ) {
var layoutPath = parseViewOrLayoutPath( path, 'layout' );
+ frameworkTrace( 'layout( #path# ) called - rendering #viewPath#' );
return internalLayout( layoutPath, body );
}
@@ -534,8 +563,14 @@ component {
* in the code...
*/
public void function onError( any exception, string event ) {
-
try {
+ if ( !structKeyExists( variables, 'framework' ) ||
+ !structKeyExists( variables.framework, 'version' ) ) {
+ // error occurred before framework was initialized
+ failure( exception, event, false, true );
+ return;
+ }
+
// record details of the exception:
if ( structKeyExists( request, 'action' ) ) {
request.failedAction = request.action;
@@ -543,11 +578,14 @@ component {
request.exception = exception;
request.event = event;
// reset lifecycle flags:
- structDelete( request, 'controllerExecutionComplete' );
- structDelete( request, 'controllerExecutionStarted' );
- structDelete( request, 'serviceExecutionComplete' );
+ structDelete( request._fw1, 'controllerExecutionComplete' );
+ structDelete( request._fw1, 'controllerExecutionStarted' );
+ structDelete( request._fw1, 'serviceExecutionComplete' );
+ structDelete( request._fw1, 'overrideViewAction' );
// setup the new controller action, based on the error action:
- structDelete( request, 'controllers' );
+ request._fw1.controllers = [ ];
+ // reset services for this new action:
+ request._fw1.services = [ ];
if ( structKeyExists( variables, 'framework' ) && structKeyExists( variables.framework, 'error' ) ) {
request.action = variables.framework.error;
@@ -561,12 +599,28 @@ component {
if ( !structKeyExists( request, 'context' ) ) {
request.context = { };
}
-
+ if ( !structKeyExists( request, 'base' ) ) {
+ if ( structKeyExists( variables, 'framework' ) && structKeyExists( variables.framework, 'base' ) ) {
+ request.base = variables.framework.base;
+ } else {
+ request.base = '';
+ }
+ }
+ if ( !structKeyExists( request, 'cfcbase' ) ) {
+ if ( structKeyExists( variables, 'framework' ) && structKeyExists( variables.framework, 'cfcbase' ) ) {
+ request.cfcbase = variables.framework.cfcbase;
+ } else {
+ request.cfcbase = '';
+ }
+ }
+ frameworkTrace( 'onError( #exception.message#, #event# ) called' );
setupRequestWrapper( false );
onRequest( '' );
+ frameworkTraceRender();
} catch ( any e ) {
failure( e, 'onError' );
failure( exception, event, true );
+ frameworkTraceRender();
}
}
@@ -575,11 +629,12 @@ component {
* this can be overridden if you want to change the behavior when
* FW/1 cannot find a matching view
*/
- public void function onMissingView( struct rc ) {
+ public string function onMissingView( struct rc ) {
// unable to find a matching view - fail with a nice exception
viewNotFound();
// if we got here, we would return the string to be rendered
// but viewNotFound() throws an exception...
+ // for example, return view( 'main.missing' );
}
/*
@@ -606,76 +661,88 @@ component {
var once = { };
var n = 0;
- request.controllerExecutionStarted = true;
+ request._fw1.controllerExecutionStarted = true;
try {
- if ( structKeyExists( request, 'controllers' ) ) {
- n = arrayLen( request.controllers );
- for ( i = 1; i <= n; i = i + 1 ) {
- tuple = request.controllers[ i ];
- // run before once per controller:
- if ( !structKeyExists( once, tuple.key ) ) {
- once[ tuple.key ] = i;
- doController( tuple.controller, 'before' );
- if ( structKeyExists( request._fw1, "abortController" ) ) abortController();
- }
- doController( tuple.controller, 'start' & tuple.item );
- if ( structKeyExists( request._fw1, "abortController" ) ) abortController();
- doController( tuple.controller, tuple.item );
- if ( structKeyExists( request._fw1, "abortController" ) ) abortController();
+ n = arrayLen( request._fw1.controllers );
+ for ( i = 1; i <= n; i = i + 1 ) {
+ tuple = request._fw1.controllers[ i ];
+ // run before once per controller:
+ if ( !structKeyExists( once, tuple.key ) ) {
+ once[ tuple.key ] = i;
+ doController( tuple, 'before', 'before' );
+ if ( structKeyExists( request._fw1, 'abortController' ) ) abortController();
}
+ doController( tuple, 'start' & tuple.item, 'start' );
+ if ( structKeyExists( request._fw1, 'abortController' ) ) abortController();
+ doController( tuple, tuple.item, 'item' );
+ if ( structKeyExists( request._fw1, 'abortController' ) ) abortController();
}
- n = arrayLen( request.services );
+ n = arrayLen( request._fw1.services );
for ( i = 1; i <= n; i = i + 1 ) {
- tuple = request.services[i];
+ tuple = request._fw1.services[ i ];
if ( tuple.key == '' ) {
// throw the result away:
- doService( tuple.service, tuple.item, tuple.args, tuple.enforceExistence );
- if ( structKeyExists( request._fw1, "abortController" ) ) abortController();
+ doService( tuple, tuple.item, tuple.args, tuple.enforceExistence );
+ if ( structKeyExists( request._fw1, 'abortController' ) ) abortController();
} else {
- _data_fw1 = doService( tuple.service, tuple.item, tuple.args, tuple.enforceExistence );
- if ( structKeyExists( request._fw1, "abortController" ) ) abortController();
+ _data_fw1 = doService( tuple, tuple.item, tuple.args, tuple.enforceExistence );
+ if ( structKeyExists( request._fw1, 'abortController' ) ) abortController();
if ( isDefined('_data_fw1') ) {
+ frameworkTrace( 'store service result in rc.#tuple.key#', tuple.subsystem, tuple.section, tuple.item );
request.context[ tuple.key ] = _data_fw1;
- }
+ } else {
+ frameworkTrace( 'service returned no result for rc.#tuple.key#', tuple.subsystem, tuple.section, tuple.item );
+ }
}
}
- request.serviceExecutionComplete = true;
- if ( structKeyExists( request, 'controllers' ) ) {
- n = arrayLen( request.controllers );
- for ( i = n; i >= 1; i = i - 1 ) {
- tuple = request.controllers[ i ];
- doController( tuple.controller, 'end' & tuple.item );
- if ( structKeyExists( request._fw1, "abortController" ) ) abortController();
- if ( once[ tuple.key ] eq i ) {
- doController( tuple.controller, 'after' );
- if ( structKeyExists( request._fw1, "abortController" ) ) abortController();
- }
+ request._fw1.serviceExecutionComplete = true;
+ n = arrayLen( request._fw1.controllers );
+ for ( i = n; i >= 1; i = i - 1 ) {
+ tuple = request._fw1.controllers[ i ];
+ doController( tuple, 'end' & tuple.item, 'end' );
+ if ( structKeyExists( request._fw1, 'abortController' ) ) abortController();
+ if ( once[ tuple.key ] eq i ) {
+ doController( tuple, 'after', 'after' );
+ if ( structKeyExists( request._fw1, 'abortController' ) ) abortController();
}
}
} catch ( FW1.AbortControllerException e ) {
- request.serviceExecutionComplete = true;
+ request._fw1.serviceExecutionComplete = true;
}
- request.controllerExecutionComplete = true;
-
- buildViewAndLayoutQueue();
+ request._fw1.controllerExecutionComplete = true;
+ buildViewQueue();
+ frameworkTrace( 'setupView() called' );
setupView();
-
- if ( structKeyExists(request, 'view') ) {
- out = internalView( request.view );
+ if ( structKeyExists(request._fw1, 'view') ) {
+ frameworkTrace( 'rendering #request._fw1.view#' );
+ out = internalView( request._fw1.view );
} else {
+ frameworkTrace( 'onMissingView() called' );
out = onMissingView( request.context );
}
- for ( i = 1; i <= arrayLen(request.layouts); i = i + 1 ) {
+
+ buildLayoutQueue();
+ for ( i = 1; i <= arrayLen(request._fw1.layouts); i = i + 1 ) {
if ( structKeyExists(request, 'layout') && !request.layout ) {
+ frameworkTrace( 'aborting layout rendering' );
break;
}
- out = internalLayout( request.layouts[i], out );
+ frameworkTrace( 'rendering #request._fw1.layouts[i]#' );
+ out = internalLayout( request._fw1.layouts[i], out );
}
writeOutput( out );
setupResponseWrapper();
}
+ /*
+ * if you override onRequestEnd(), call super.onRequestEnd() if you
+ * want tracing functionality to continue working
+ */
+ public any function onRequestEnd() {
+ frameworkTraceRender();
+ }
+
/*
* it is better to set up your request configuration in
* your setupRequest() method
@@ -683,9 +750,6 @@ component {
* super.onRequestStart() first
*/
public any function onRequestStart( string targetPath ) {
-
- var pathInfo = variables.cgiPathInfo;
-
setupFrameworkDefaults();
setupRequestDefaults();
@@ -693,59 +757,6 @@ component {
setupApplicationWrapper();
}
- if ( !structKeyExists(request, 'context') ) {
- request.context = { };
- }
- // SES URLs by popular request :)
- if ( len( pathInfo ) > len( variables.cgiScriptName ) && left( pathInfo, len( variables.cgiScriptName ) ) == variables.cgiScriptName ) {
- // canonicalize for IIS:
- pathInfo = right( pathInfo, len( pathInfo ) - len( variables.cgiScriptName ) );
- } else if ( len( pathInfo ) > 0 && pathInfo == left( variables.cgiScriptName, len( pathInfo ) ) ) {
- // pathInfo is bogus so ignore it:
- pathInfo = '';
- }
- pathInfo = processRoutes( pathInfo );
- try {
- // we use .split() to handle empty items in pathInfo - we fallback to listToArray() on
- // any system that doesn't support .split() just in case (empty items won't work there!)
- if ( len( pathInfo ) > 1 ) {
- pathInfo = right( pathInfo, len( pathInfo ) - 1 ).split( '/' );
- } else {
- pathInfo = arrayNew( 1 );
- }
- } catch ( any exception ) {
- pathInfo = listToArray( pathInfo, '/' );
- }
- var sesN = arrayLen( pathInfo );
- if ( ( sesN > 0 || variables.framework.generateSES ) && getBaseURL() != 'useRequestURI' ) {
- request.generateSES = true;
- }
- for ( var sesIx = 1; sesIx <= sesN; sesIx = sesIx + 1 ) {
- if ( sesIx == 1 ) {
- request.context[variables.framework.action] = pathInfo[sesIx];
- } else if ( sesIx == 2 ) {
- request.context[variables.framework.action] = pathInfo[sesIx-1] & '.' & pathInfo[sesIx];
- } else if ( sesIx mod 2 == 1 ) {
- request.context[ pathInfo[sesIx] ] = '';
- } else {
- request.context[ pathInfo[sesIx-1] ] = pathInfo[sesIx];
- }
- }
- // certain remote calls do not have URL or form scope:
- if ( isDefined('URL') ) structAppend(request.context,URL);
- if ( isDefined('form') ) structAppend(request.context,form);
- // figure out the request action before restoring flash context:
- if ( !structKeyExists(request.context, variables.framework.action) ) {
- request.context[variables.framework.action] = variables.framework.home;
- } else {
- request.context[variables.framework.action] = getFullyQualifiedAction( request.context[variables.framework.action] );
- }
- if ( variables.framework.noLowerCase ) {
- request.action = validateAction( request.context[variables.framework.action] );
- } else {
- request.action = validateAction( lCase(request.context[variables.framework.action]) );
- }
-
restoreFlashContext();
// ensure flash context cannot override request action:
request.context[variables.framework.action] = request.action;
@@ -753,12 +764,16 @@ component {
// allow configured extensions and paths to pass through to the requested template.
// NOTE: for unhandledPaths, we make the list into an escaped regular expression so we match on subdirectories.
// Meaning /myexcludepath will match '/myexcludepath' and all subdirectories
- if ( listFindNoCase( framework.unhandledExtensions, listLast( targetPath, '.' ) ) ||
- REFindNoCase( '^(' & framework.unhandledPathRegex & ')', targetPath ) ) {
+ if ( listFindNoCase( variables.framework.unhandledExtensions, listLast( targetPath, '.' ) ) ||
+ REFindNoCase( '^(' & variables.framework.unhandledPathRegex & ')', targetPath ) ) {
structDelete(this, 'onRequest');
structDelete(variables, 'onRequest');
- structDelete(this, 'onError');
- structDelete(variables, 'onError');
+ structDelete(this, 'onRequestEnd');
+ structDelete(variables, 'onRequestEnd');
+ if ( !variables.framework.unhandledErrorCaught ) {
+ structDelete(this, 'onError');
+ structDelete(variables, 'onError');
+ }
} else {
setupRequestWrapper( true );
}
@@ -773,11 +788,11 @@ component {
public any function onSessionStart() {
setupFrameworkDefaults();
setupRequestDefaults();
- setupSession();
+ setupSessionWrapper();
}
// populate() may be invoked inside controllers
- public any function populate( any cfc, string keys = '', boolean trustKeys = false, boolean trim = false ) {
+ public any function populate( any cfc, string keys = '', boolean trustKeys = false, boolean trim = false, deep = false ) {
if ( keys == '' ) {
if ( trustKeys ) {
// assume everything in the request context can be set into the CFC
@@ -786,8 +801,8 @@ component {
var args = { };
args[ property ] = request.context[ property ];
if ( trim && isSimpleValue( args[ property ] ) ) args[ property ] = trim( args[ property ] );
- // cfc[ 'set'&property ]( argumentCollection = args ); // ugh! no portable script version of this?!?!
- evaluate( 'cfc.set#property#( argumentCollection = args )' );
+ // cfc[ 'set'&property ]( argumentCollection = args ); // ugh! no portable script version of this?!?!
+ setProperty( cfc, property, args );
} catch ( any e ) {
onPopulateError( cfc, property, request.context );
}
@@ -800,7 +815,18 @@ component {
args[ property ] = request.context[ property ];
if ( trim && isSimpleValue( args[ property ] ) ) args[ property ] = trim( args[ property ] );
// cfc[ 'set'&property ]( argumentCollection = args ); // ugh! no portable script version of this?!?!
- evaluate( 'cfc.set#property#( argumentCollection = args )' );
+ setProperty( cfc, property, args );
+ } else if ( deep && structKeyExists( cfc, 'get' & property ) ) {
+ //look for a context property that starts with the property
+ for ( var key in request.context ) {
+ if ( listFindNoCase( key, property, '.') ) {
+ try {
+ setProperty( cfc, key, { '#key#' = request.context[ key ] } );
+ } catch ( any e ) {
+ onPopulateError( cfc, key, request.context);
+ }
+ }
+ }
}
}
}
@@ -815,16 +841,44 @@ component {
args[ trimProperty ] = request.context[ trimProperty ];
if ( trim && isSimpleValue( args[ trimProperty ] ) ) args[ trimProperty ] = trim( args[ trimProperty ] );
// cfc[ 'set'&trimproperty ]( argumentCollection = args ); // ugh! no portable script version of this?!?!
- evaluate( 'cfc.set#trimProperty#( argumentCollection = args )' );
+ setProperty( cfc, trimProperty, args );
+ }
+ } else if ( deep ) {
+ if ( listLen( trimProperty, '.' ) > 1 ) {
+ var prop = listFirst( trimProperty, '.' );
+ if ( structKeyExists( cfc, 'get' & prop ) ) {
+ setProperty( cfc, trimProperty, { '#trimProperty#' = request.context[ trimProperty ] } );
+ }
}
}
}
}
return cfc;
}
+
+ private void function setProperty( struct cfc, string property, struct args ) {
+ if ( listLen( property, '.' ) > 1 ) {
+ var firstObjName = listFirst( property, '.' );
+ var newProperty = listRest( property, '.' );
+
+ args[ newProperty ] = args[ property ];
+ structDelete( args, property );
+
+ if ( structKeyExists( cfc , 'get' & firstObjName ) ) {
+ var obj = getProperty( cfc, firstObjName );
+ if ( !isNull( obj ) ) setProperty( obj, newProperty, args );
+ }
+ } else {
+ evaluate( 'cfc.set#property#( argumentCollection = args )' );
+ }
+ }
+ private any function getProperty( struct cfc, string property ) {
+ if ( structKeyExists( cfc, 'get#property#' ) ) return evaluate( 'cfc.get#property#()' );
+ }
+
// call from your controller to redirect to a clean URL based on an action, pushing data to flash scope if necessary:
- public void function redirect( string action, string preserve = 'none', string append = 'none', string path = variables.magicBaseURL, string queryString = '' ) {
+ public void function redirect( string action, string preserve = 'none', string append = 'none', string path = variables.magicBaseURL, string queryString = '', string statusCode = '302' ) {
if ( path == variables.magicBaseURL ) path = getBaseURL();
var preserveKey = '';
if ( preserve != 'none' ) {
@@ -875,7 +929,11 @@ component {
}
}
setupResponseWrapper();
- location( targetURL, false );
+ if ( variables.framework.trace ) {
+ frameworkTrace( 'redirecting to #targetURL# (#statusCode#)' );
+ session._fw1_trace = request._fw1.trace;
+ }
+ location( targetURL, false, statusCode );
}
// call this from your controller to queue up additional services
@@ -885,21 +943,24 @@ component {
var item = getItem( action );
var tuple = { };
- if ( structKeyExists( request, "serviceExecutionComplete" ) ) {
- raiseException( type="FW1.serviceExecutionComplete", message="Service '#action#' may not be added at this point.",
- detail="The service execution phase is complete. Services may not be added by end*() or after() controller methods." );
+ if ( structKeyExists( request._fw1, 'serviceExecutionComplete' ) ) {
+ raiseException( type='FW1.serviceExecutionComplete', message="Service '#action#' may not be added at this point.",
+ detail='The service execution phase is complete. Services may not be added by end*() or after() controller methods.' );
}
tuple.service = getService(section=section, subsystem=subsystem);
+ tuple.subsystem = subsystem;
+ tuple.section = section;
tuple.item = item;
tuple.key = key;
tuple.args = args;
tuple.enforceExistence = enforceExistence;
- if ( structKeyExists( tuple, "service" ) && isObject( tuple.service ) ) {
- arrayAppend( request.services, tuple );
+ if ( structKeyExists( tuple, 'service' ) && isObject( tuple.service ) ) {
+ frameworkTrace( 'queuing service', subsystem, section, item );
+ arrayAppend( request._fw1.services, tuple );
} else if ( enforceExistence ) {
- raiseException( type="FW1.serviceCfcNotFound", message="Service '#action#' does not exist.",
+ raiseException( type='FW1.serviceCfcNotFound', message="Service '#action#' does not exist.",
detail="To have the execution of this service be conditional based upon its existence, pass in a third parameter of 'false'." );
}
}
@@ -919,7 +980,7 @@ component {
* use this to override the default layout
*/
public void function setLayout( string action ) {
- request.overrideLayoutAction = validateAction( action );
+ request._fw1.overrideLayoutAction = validateAction( action );
}
/*
@@ -944,6 +1005,12 @@ component {
*/
public void function setupApplication() { }
+ /*
+ * override this to provide environment-specific initialization
+ * you do not need to call super.setupEnvironment()
+ */
+ public void function setupEnvironment( string env ) { }
+
/*
* override this to provide request-specific initialization
* you do not need to call super.setupRequest()
@@ -983,7 +1050,7 @@ component {
* use this to override the default view
*/
public void function setView( string action ) {
- request.overrideViewAction = validateAction( action );
+ request._fw1.overrideViewAction = validateAction( action );
}
/*
@@ -998,7 +1065,8 @@ component {
* returns the UI generated by the named view
*/
public string function view( string path, struct args = { } ) {
- var viewPath = parseViewOrLayoutPath( path, "view" );
+ var viewPath = parseViewOrLayoutPath( path, 'view' );
+ frameworkTrace( 'view( #path# ) called - rendering #viewPath#' );
return internalView( viewPath, args );
}
@@ -1016,7 +1084,7 @@ component {
}
}
- private void function buildViewAndLayoutQueue() {
+ private void function buildLayoutQueue() {
var siteWideLayoutBase = request.base & getSubsystemDirPrefix( variables.framework.siteWideLayoutSubsystem );
var testLayout = 0;
// default behavior:
@@ -1025,77 +1093,95 @@ component {
var item = request.item;
var subsystembase = '';
- // has view been overridden?
- if ( structKeyExists( request, 'overrideViewAction' ) ) {
- subsystem = getSubsystem( request.overrideViewAction );
- section = getSection( request.overrideViewAction );
- item = getItem( request.overrideViewAction );
- structDelete( request, 'overrideViewAction' );
- }
- subsystembase = request.base & getSubsystemDirPrefix( subsystem );
-
- // view and layout setup - used to be in setupRequestWrapper():
- request.view = parseViewOrLayoutPath( subsystem & variables.framework.subsystemDelimiter &
- section & '/' & item, 'view' );
- if ( !cachedFileExists( expandPath( request.view ) ) ) {
- request.missingView = request.view;
- // ensures original view not re-invoked for onError() case:
- structDelete( request, 'view' );
- }
-
- request.layouts = [ ];
+ request._fw1.layouts = [ ];
// has layout been overridden?
- if ( structKeyExists( request, 'overrideLayoutAction' ) ) {
- subsystem = getSubsystem( request.overrideLayoutAction );
- section = getSection( request.overrideLayoutAction );
- item = getItem( request.overrideLayoutAction );
- structDelete( request, 'overrideLayoutAction' );
+ if ( structKeyExists( request._fw1, 'overrideLayoutAction' ) ) {
+ subsystem = getSubsystem( request._fw1.overrideLayoutAction );
+ section = getSection( request._fw1.overrideLayoutAction );
+ item = getItem( request._fw1.overrideLayoutAction );
+ structDelete( request._fw1, 'overrideLayoutAction' );
}
subsystembase = request.base & getSubsystemDirPrefix( subsystem );
-
+ frameworkTrace( 'building layout queue', subsystem, section, item );
// look for item-specific layout:
testLayout = parseViewOrLayoutPath( subsystem & variables.framework.subsystemDelimiter &
section & '/' & item, 'layout' );
- if ( cachedFileExists( expandPath( testLayout ) ) ) {
- arrayAppend( request.layouts, testLayout );
- }
+ if ( cachedFileExists( testLayout ) ) {
+ frameworkTrace( 'found item-specific layout #testLayout#', subsystem, section, item );
+ arrayAppend( request._fw1.layouts, testLayout );
+ }
// look for section-specific layout:
testLayout = parseViewOrLayoutPath( subsystem & variables.framework.subsystemDelimiter &
section, 'layout' );
- if ( cachedFileExists( expandPath( testLayout ) ) ) {
- arrayAppend( request.layouts, testLayout );
+ if ( cachedFileExists( testLayout ) ) {
+ frameworkTrace( 'found section-specific layout #testLayout#', subsystem, section, item );
+ arrayAppend( request._fw1.layouts, testLayout );
}
// look for subsystem-specific layout (site-wide layout if not using subsystems):
if ( request.section != 'default' ) {
testLayout = parseViewOrLayoutPath( subsystem & variables.framework.subsystemDelimiter &
'default', 'layout' );
- if ( cachedFileExists( expandPath( testLayout ) ) ) {
- arrayAppend( request.layouts, testLayout );
+ if ( cachedFileExists( testLayout ) ) {
+ frameworkTrace( 'found default layout #testLayout#', subsystem, section, item );
+ arrayAppend( request._fw1.layouts, testLayout );
}
}
// look for site-wide layout (only applicable if using subsystems)
if ( usingSubsystems() && siteWideLayoutBase != subsystembase ) {
testLayout = parseViewOrLayoutPath( variables.framework.siteWideLayoutSubsystem & variables.framework.subsystemDelimiter &
'default', 'layout' );
- if ( cachedFileExists( expandPath( testLayout ) ) ) {
- arrayAppend( request.layouts, testLayout );
+ if ( cachedFileExists( testLayout ) ) {
+ frameworkTrace( 'found #variables.framework.siteWideLayoutSubsystem# layout #testLayout#', subsystem, section, item );
+ arrayAppend( request._fw1.layouts, testLayout );
}
}
}
+
+ private void function buildViewQueue() {
+ // default behavior:
+ var subsystem = request.subsystem;
+ var section = request.section;
+ var item = request.item;
+ var subsystembase = '';
+
+ // has view been overridden?
+ if ( structKeyExists( request._fw1, 'overrideViewAction' ) ) {
+ subsystem = getSubsystem( request._fw1.overrideViewAction );
+ section = getSection( request._fw1.overrideViewAction );
+ item = getItem( request._fw1.overrideViewAction );
+ structDelete( request._fw1, 'overrideViewAction' );
+ }
+ subsystembase = request.base & getSubsystemDirPrefix( subsystem );
+ frameworkTrace( 'building view queue', subsystem, section, item );
+ // view and layout setup - used to be in setupRequestWrapper():
+ request._fw1.view = parseViewOrLayoutPath( subsystem & variables.framework.subsystemDelimiter &
+ section & '/' & item, 'view' );
+ if ( cachedFileExists( request._fw1.view ) ) {
+ frameworkTrace( 'found view #request._fw1.view#', subsystem, section, item );
+ } else {
+ frameworkTrace( 'no such view #request._fw1.view#', subsystem, section, item );
+ request.missingView = request._fw1.view;
+ // ensures original view not re-invoked for onError() case:
+ structDelete( request._fw1, 'view' );
+ }
+ }
+
+
private boolean function cachedFileExists( string filePath ) {
var cache = application[ variables.framework.applicationKey ].cache;
if ( !variables.framework.cacheFileExists ) {
- return fileExists( filePath );
+ return fileExists( expandPath( filePath) );
}
param name="cache.fileExists" default="#{ }#";
if ( !structKeyExists( cache.fileExists, filePath ) ) {
- cache.fileExists[ filePath ] = fileExists( filePath );
+ cache.fileExists[ filePath ] = fileExists( expandPath( filePath ) );
}
return cache.fileExists[ filePath ];
}
+
private string function cfcFilePath( string dottedPath ) {
if ( dottedPath == '' ) {
return '/';
@@ -1104,21 +1190,36 @@ component {
}
}
- private void function doController( any cfc, string method ) {
- if ( structKeyExists( cfc, method ) || structKeyExists( cfc, 'onMissingMethod' ) ) {
+ private void function doController( struct tuple, string method, string lifecycle ) {
+ var cfc = tuple.controller;
+ if ( structKeyExists( cfc, method ) ) {
try {
+ frameworkTrace( 'calling #lifecycle# controller', tuple.subsystem, tuple.section, method );
evaluate( 'cfc.#method#( rc = request.context )' );
} catch ( any e ) {
setCfcMethodFailureInfo( cfc, method );
rethrow;
}
}
+ else if ( structKeyExists( cfc, 'onMissingMethod' ) ) {
+ try {
+ frameworkTrace( 'calling #lifecycle# controller (via onMissingMethod)', tuple.subsystem, tuple.section, method );
+ evaluate( 'cfc.#method#( rc = request.context, method = lifecycle )' );
+ } catch ( any e ) {
+ setCfcMethodFailureInfo( cfc, method );
+ rethrow;
+ }
+ } else {
+ frameworkTrace( 'no #lifecycle# controller to call', tuple.subsystem, tuple.section, method );
+ }
}
- private any function doService( any cfc, string method, struct args, boolean enforceExistence ) {
+ private any function doService( struct tuple, string method, struct args, boolean enforceExistence ) {
+ var cfc = tuple.service;
if ( structKeyExists( cfc, method ) || structKeyExists( cfc, 'onMissingMethod' ) ) {
try {
structAppend( args, request.context, false );
+ frameworkTrace( 'calling service', tuple.subsystem, tuple.section, method );
var _result_fw1 = evaluate( 'cfc.#method#( argumentCollection = args )' );
if ( !isNull( _result_fw1 ) ) {
return _result_fw1;
@@ -1128,7 +1229,7 @@ component {
rethrow;
}
} else if ( enforceExistence ) {
- raiseException( type="FW1.serviceMethodNotFound", message="Service method '#method#' does not exist in service '#getMetadata( cfc ).fullname#'.",
+ raiseException( type='FW1.serviceMethodNotFound', message="Service method '#method#' does not exist in service '#getMetadata( cfc ).fullname#'.",
detail="To have the execution of this service method be conditional based upon its existence, pass in a third parameter of 'false'." );
}
}
@@ -1151,17 +1252,23 @@ component {
}
- private void function failure( any exception, string event, boolean indirect = false ) {
+ private void function failure( any exception, string event, boolean indirect = false, boolean early = false ) {
var h = indirect ? 3 : 1;
if ( structKeyExists(exception, 'rootCause') ) {
exception = exception.rootCause;
}
- writeOutput( "" & ( indirect ? "Original exception " : "Exception" ) & " in #event#" );
- if ( structKeyExists( request, 'failedAction' ) ) {
- writeOutput( "
The action #request.failedAction# failed.
" );
+ getPageContext().getResponse().setStatus( 500 );
+ if ( arguments.early ) {
+ writeOutput( 'Exception occured before FW/1 was initialized
');
+ } else {
+ writeOutput( '' & ( indirect ? 'Original exception ' : 'Exception' ) & ' in #event#' );
+ if ( structKeyExists( request, 'failedAction' ) ) {
+ writeOutput( 'The action #request.failedAction# failed.
' );
+ }
+ writeOutput( '#exception.message#' );
}
- writeOutput( "#exception.message#" );
- writeOutput( "#exception.detail# (#exception.type#)
" );
+
+ writeOutput( '#exception.detail# (#exception.type#)
' );
dumpException(exception);
}
@@ -1214,6 +1321,64 @@ component {
return setters;
}
+ private void function frameworkTrace( string message, string subsystem = '', string section = '', string item = '' ) {
+ if ( variables.framework.trace ) {
+ if ( isDefined( 'session._fw1_trace' ) && structKeyExists( session, '_fw1_trace' ) ) {
+ request._fw1.trace = session._fw1_trace;
+ structDelete( session, '_fw1_trace' );
+ }
+ arrayAppend( request._fw1.trace, { tick = getTickCount(), msg = message, sub = subsystem, s = section, i = item } );
+ }
+ }
+
+ private void function frameworkTraceRender() {
+ if ( variables.framework.trace && arrayLen( request._fw1.trace ) ) {
+ var startTime = request._fw1.trace[1].tick;
+ var font = 'font-family: verdana, helvetica;';
+ writeOutput( '
' );
+ writeOutput( '
Framework Lifecycle Trace
' );
+ var table = '
';
+ writeOutput( table );
+ var colors = [ '##ccd4dd', '##ccddcc' ];
+ var row = 0;
+ var n = arrayLen( request._fw1.trace );
+ for ( var i = 1; i <= n; ++i ) {
+ var trace = request._fw1.trace[i];
+ var action = '';
+ if ( trace.s == variables.magicApplicationController || trace.sub == variables.magicApplicationSubsystem ) {
+ action = 'Application.cfc';
+ if ( right( trace.i, len( variables.magicApplicationAction ) ) == variables.magicApplicationAction ) {
+ continue;
+ }
+ } else {
+ action = trace.sub;
+ if ( action != '' && trace.s != '' ) {
+ action &= variables.framework.subsystemDelimiter;
+ }
+ action &= trace.s;
+ if ( trace.s != '' ) {
+ action &= '.';
+ }
+ action &= trace.i;
+ }
+ ++row;
+ writeOutput( '' );
+ writeOutput( '#trace.tick - startTime#ms | ' );
+ writeOutput( '#action# | ' );
+ var color =
+ trace.msg.startsWith( 'no ' ) ? '##cc8888' :
+ trace.msg.startsWith( 'onError( ' ) ? '##cc0000' : '##0000';
+ writeOutput( '#trace.msg# | ' );
+ writeOutput( '
' );
+ if ( trace.msg.startsWith( 'redirecting ' ) ) {
+ writeOutput( '
#table#' );
+ if ( i < n ) startTime = request._fw1.trace[i+1].tick;
+ }
+ }
+ writeOutput( '
' );
+ }
+ }
+
private any function getCachedComponent( string type, string subsystem, string section ) {
setupSubsystemWrapper( subsystem );
@@ -1239,7 +1404,7 @@ component {
if ( type == 'controller' && section == variables.magicApplicationController ) {
// treat this (Application.cfc) as a controller:
cfc = this;
- } else if ( cachedFileExists( expandPath( cfcFilePath( request.cfcbase ) & subsystemDir & types & '/' & section & '.cfc' ) ) ) {
+ } else if ( cachedFileExists( cfcFilePath( request.cfcbase ) & subsystemDir & types & '/' & section & '.cfc' ) ) {
// we call createObject() rather than new so we can control initialization:
if ( request.cfcbase == '' ) {
cfc = createObject( 'component', subsystemDot & types & '.' & section );
@@ -1281,20 +1446,24 @@ component {
private string function getNextPreserveKeyAndPurgeOld() {
var nextPreserveKey = '';
var oldKeyToPurge = '';
- if ( variables.framework.maxNumContextsPreserved > 1 ) {
- lock scope="session" type="exclusive" timeout="30" {
- param name="session.__fw1NextPreserveKey" default="1";
- nextPreserveKey = session.__fw1NextPreserveKey;
- session.__fw1NextPreserveKey = session.__fw1NextPreserveKey + 1;
- }
- oldKeyToPurge = nextPreserveKey - variables.framework.maxNumContextsPreserved;
- } else {
- lock scope="session" type="exclusive" timeout="30" {
- session.__fw1PreserveKey = '';
- nextPreserveKey = session.__fw1PreserveKey;
- }
- oldKeyToPurge = '';
- }
+ try {
+ if ( variables.framework.maxNumContextsPreserved > 1 ) {
+ lock scope="session" type="exclusive" timeout="30" {
+ param name="session.__fw1NextPreserveKey" default="1";
+ nextPreserveKey = session.__fw1NextPreserveKey;
+ session.__fw1NextPreserveKey = session.__fw1NextPreserveKey + 1;
+ }
+ oldKeyToPurge = nextPreserveKey - variables.framework.maxNumContextsPreserved;
+ } else {
+ lock scope="session" type="exclusive" timeout="30" {
+ session.__fw1PreserveKey = '';
+ nextPreserveKey = session.__fw1PreserveKey;
+ }
+ oldKeyToPurge = '';
+ }
+ } catch ( any e ) {
+ // ignore - assume session scope is disabled
+ }
var key = getPreserveKeySessionKey( oldKeyToPurge );
if ( structKeyExists( session, key ) ) {
structDelete( session, key );
@@ -1340,9 +1509,9 @@ component {
if ( structKeyExists( rc, '$' ) ) {
$ = rc.$;
}
- if ( !structKeyExists( request, 'controllerExecutionComplete' ) ) {
- raiseException( type="FW1.layoutExecutionFromController", message="Invalid to call the layout method at this point.",
- detail="The layout method should not be called prior to the completion of the controller execution phase." );
+ if ( !structKeyExists( request._fw1, 'controllerExecutionComplete' ) ) {
+ raiseException( type='FW1.layoutExecutionFromController', message='Invalid to call the layout method at this point.',
+ detail='The layout method should not be called prior to the completion of the controller execution phase.' );
}
var response = '';
savecontent variable="response" {
@@ -1359,9 +1528,10 @@ component {
$ = rc.$;
}
structAppend( local, args );
- if ( !structKeyExists( request, 'serviceExecutionComplete') && arrayLen( request.services ) != 0 ) {
- raiseException( type="FW1.viewExecutionFromController", message="Invalid to call the view method at this point.",
- detail="The view method should not be called prior to the completion of the service execution phase." );
+ if ( !structKeyExists( request._fw1, 'serviceExecutionComplete') &&
+ structKeyExists( request._fw1, 'services' ) && arrayLen( request._fw1.services ) != 0 ) {
+ raiseException( type='FW1.viewExecutionFromController', message='Invalid to call the view method at this point.',
+ detail='The view method should not be called prior to the completion of the service execution phase.' );
}
var response = '';
savecontent variable="response" {
@@ -1431,27 +1601,31 @@ component {
}
if ( route == '*' ) {
route = '/';
- } else if ( right( route, 1 ) != '/' ) {
+ } else if ( right( route, 1 ) != '/' && right( route, 1 ) != '$' ) {
+ // only add the closing backslash if last position is not already a "/" or a "$" to respect regex end of string
route &= '/';
}
} else {
route = '/';
}
if ( !len( target ) || right( target, 1) != '/' ) target &= '/';
- // walk for :var and replace with ([^/]*) in route and back reference in target:
+ // walk for self defined (regex) and :var - replace :var with ([^/]*) in route and back reference in target:
var n = 1;
- var placeholders = rematch( ':[^/]+', route );
+ var placeholders = rematch( '(:[^/]+)|(\([^\)]+)', route );
for ( var placeholder in placeholders ) {
- route = replace( route, placeholder, '([^/]*)' );
- target = replace( target, placeholder, chr(92) & n );
+ if ( left( placeholder, 1 ) == ':') {
+ route = replace( route, placeholder, '([^/]*)' );
+ target = replace( target, placeholder, chr(92) & n );
+ }
++n;
}
- // add trailing match/back reference:
- route &= '(.*)';
+ // add trailing match/back reference: if last character is not "$" to respect regex end of string
+ if (right( route, 1 ) != '$')
+ route &= '(.*)';
target &= chr(92) & n;
// end of preprocessing section
if ( !len( path ) || right( path, 1) != '/' ) path &= '/';
- var matched = len( routeMatch.method ) ? ( '$' & CGI.REQUEST_METHOD == routeMatch.method ) : true;
+ var matched = len( routeMatch.method ) ? ( '$' & request._fw1.cgiRequestMethod == routeMatch.method ) : true;
if ( matched && reFind( route, path ) ) {
routeMatch.matched = true;
routeMatch.pattern = route;
@@ -1575,6 +1749,7 @@ component {
}
// this will recreate the main bean factory on a reload:
+ frameworkTrace( 'setupApplication() called' );
setupApplication();
if ( isReload ) {
@@ -1680,6 +1855,9 @@ component {
if ( !structKeyExists(variables.framework, 'unhandledPaths') ) {
variables.framework.unhandledPaths = '/flex2gateway';
}
+ if ( !structKeyExists( variables.framework, 'unhandledErrorCaught' ) ) {
+ variables.framework.unhandledErrorCaught = false;
+ }
// convert unhandledPaths to regex:
variables.framework.unhandledPathRegex = replaceNoCase(
REReplace( variables.framework.unhandledPaths, '(\+|\*|\?|\.|\[|\^|\$|\(|\)|\{|\||\\)', '\\\1', 'all' ),
@@ -1702,12 +1880,88 @@ component {
if ( !structKeyExists( variables.framework, 'subsystems' ) ) {
variables.framework.subsystems = { };
}
- variables.framework.version = '2.0.1';
+ if ( !structKeyExists( variables.framework, 'trace' ) ) {
+ variables.framework.trace = false;
+ }
+ variables.framework.version = '2.1';
+ setupFrameworkEnvironments();
}
+ private void function setupFrameworkEnvironments() {
+ var env = getEnvironment();
+ if ( structKeyExists( variables.framework, 'environments' ) ) {
+ var envs = variables.framework.environments;
+ var tier = listFirst( env, '-' );
+ if ( structKeyExists( envs, tier ) ) {
+ structAppend( variables.framework, envs[ tier ] );
+ }
+ if ( structKeyExists( envs, env ) ) {
+ structAppend( variables.framework, envs[ env ] );
+ }
+ }
+ setupEnvironment( env );
+ }
+
private void function setupRequestDefaults() {
- request.base = variables.framework.base;
- request.cfcbase = variables.framework.cfcbase;
+ if ( !request._fw1.requestDefaultsInitialized ) {
+ var pathInfo = variables.cgiPathInfo;
+ request.base = variables.framework.base;
+ request.cfcbase = variables.framework.cfcbase;
+
+ if ( !structKeyExists(request, 'context') ) {
+ request.context = { };
+ }
+ // SES URLs by popular request :)
+ if ( len( pathInfo ) > len( variables.cgiScriptName ) && left( pathInfo, len( variables.cgiScriptName ) ) == variables.cgiScriptName ) {
+ // canonicalize for IIS:
+ pathInfo = right( pathInfo, len( pathInfo ) - len( variables.cgiScriptName ) );
+ } else if ( len( pathInfo ) > 0 && pathInfo == left( variables.cgiScriptName, len( pathInfo ) ) ) {
+ // pathInfo is bogus so ignore it:
+ pathInfo = '';
+ }
+ pathInfo = processRoutes( pathInfo );
+ try {
+ // we use .split() to handle empty items in pathInfo - we fallback to listToArray() on
+ // any system that doesn't support .split() just in case (empty items won't work there!)
+ if ( len( pathInfo ) > 1 ) {
+ pathInfo = right( pathInfo, len( pathInfo ) - 1 ).split( '/' );
+ } else {
+ pathInfo = arrayNew( 1 );
+ }
+ } catch ( any exception ) {
+ pathInfo = listToArray( pathInfo, '/' );
+ }
+ var sesN = arrayLen( pathInfo );
+ if ( ( sesN > 0 || variables.framework.generateSES ) && getBaseURL() != 'useRequestURI' ) {
+ request._fw1.generateSES = true;
+ }
+ for ( var sesIx = 1; sesIx <= sesN; sesIx = sesIx + 1 ) {
+ if ( sesIx == 1 ) {
+ request.context[variables.framework.action] = pathInfo[sesIx];
+ } else if ( sesIx == 2 ) {
+ request.context[variables.framework.action] = pathInfo[sesIx-1] & '.' & pathInfo[sesIx];
+ } else if ( sesIx mod 2 == 1 ) {
+ request.context[ pathInfo[sesIx] ] = '';
+ } else {
+ request.context[ pathInfo[sesIx-1] ] = pathInfo[sesIx];
+ }
+ }
+ // certain remote calls do not have URL or form scope:
+ if ( isDefined('URL') ) structAppend(request.context,URL);
+ if ( isDefined('form') ) structAppend(request.context,form);
+ // figure out the request action before restoring flash context:
+ if ( !structKeyExists(request.context, variables.framework.action) ) {
+ request.context[variables.framework.action] = variables.framework.home;
+ } else {
+ request.context[variables.framework.action] = getFullyQualifiedAction( request.context[variables.framework.action] );
+ }
+ if ( variables.framework.noLowerCase ) {
+ request.action = validateAction( request.context[variables.framework.action] );
+ } else {
+ request.action = validateAction( lCase(request.context[variables.framework.action]) );
+ }
+ request._fw1.requestDefaultsInitialized = true;
+ }
}
private void function setupRequestWrapper( boolean runSetup ) {
@@ -1716,12 +1970,17 @@ component {
request.subsystembase = request.base & getSubsystemDirPrefix( request.subsystem );
request.section = getSection( request.action );
request.item = getItem( request.action );
- request.services = [ ];
if ( runSetup ) {
rc = request.context;
- controller( variables.magicApplicationController & '.' & variables.magicApplicationAction );
+ if ( usingSubsystems() ) {
+ controller( variables.magicApplicationSubsystem & variables.framework.subsystemDelimiter &
+ variables.magicApplicationController & '.' & variables.magicApplicationAction );
+ } else {
+ controller( variables.magicApplicationController & '.' & variables.magicApplicationAction );
+ }
setupSubsystemWrapper( request.subsystem );
+ frameworkTrace( 'setupRequest() called' );
setupRequest();
}
@@ -1732,10 +1991,12 @@ component {
}
private void function setupResponseWrapper() {
+ frameworkTrace( 'setupResponse() called' );
setupResponse();
}
private void function setupSessionWrapper() {
+ frameworkTrace( 'setupSession() called' );
setupSession();
}
@@ -1744,6 +2005,7 @@ component {
lock name="fw1_#application.applicationName#_#variables.framework.applicationKey#_subsysteminit_#subsystem#" type="exclusive" timeout="30" {
if ( !isSubsystemInitialized( subsystem ) ) {
application[ variables.framework.applicationKey ].subsystems[ subsystem ] = now();
+ frameworkTrace( 'setupSubsystem() called', subsystem );
setupSubsystem( subsystem );
}
}
@@ -1752,14 +2014,14 @@ component {
private string function validateAction( string action ) {
// check for forward and backward slash in the action - using chr() to avoid confusing TextMate (Hi Nathan!)
if ( findOneOf( chr(47) & chr(92), action ) > 0 ) {
- raiseException( type="FW1.actionContainsSlash", message="Found a slash in the action: '#action#'.",
- detail="Actions are not allowed to embed sub-directory paths.");
+ raiseException( type='FW1.actionContainsSlash', message="Found a slash in the action: '#action#'.",
+ detail='Actions are not allowed to embed sub-directory paths.');
}
return action;
}
private void function viewNotFound() {
- raiseException( type="FW1.viewNotFound", message="Unable to find a view for '#request.action#' action.",
+ raiseException( type='FW1.viewNotFound', message="Unable to find a view for '#request.action#' action.",
detail="'#request.missingView#' does not exist." );
}
diff --git a/tests/Application.cfc b/tests/Application.cfc
new file mode 100644
index 00000000..54f5b8ff
--- /dev/null
+++ b/tests/Application.cfc
@@ -0,0 +1,6 @@
+component{
+ this.name = 'fw1 test';
+
+ this.mappings['/mxunit'] = getDirectoryFromPath(getCurrentTemplatePath()) & "../../mxunit";
+ this.mappings['/org'] = getDirectoryFromPath(getCurrentTemplatePath()) & "../org";
+}
\ No newline at end of file
diff --git a/tests/InjectableTest.cfc b/tests/InjectableTest.cfc
new file mode 100644
index 00000000..3bb42d40
--- /dev/null
+++ b/tests/InjectableTest.cfc
@@ -0,0 +1,14 @@
+component extends="mxunit.framework.TestCase" {
+
+ private any function getVariablesScope( any cfc ) {
+ cfc.__$$fetchVariables = returnVariablesScope;
+ var vars = cfc.__$$fetchVariables();
+ structDelete( cfc, "__$$fetchVariables" );
+ return vars;
+ }
+
+ private any function returnVariablesScope() {
+ return variables;
+ }
+
+}
diff --git a/tests/frameworkEnvTest.cfc b/tests/frameworkEnvTest.cfc
new file mode 100644
index 00000000..fc6418f0
--- /dev/null
+++ b/tests/frameworkEnvTest.cfc
@@ -0,0 +1,162 @@
+component extends="tests.InjectableTest" {
+
+ public void function setUp() {
+ structClear( request );
+ variables.fw = new org.corfield.framework();
+ variables.fwvars = getVariablesScope( variables.fw );
+ variables.fwvars.framework = { };
+ variables.fwcfg = variables.fwvars.framework;
+ variables.fwcfg.environments = {
+ "dev" = { reloadApplicationOnEveryRequest = true },
+ "dev-one" = { oneNewOption = 1 },
+ "dev-two" = { reloadApplicationOnEveryRequest = false },
+ "prod" = { useSSL = true }
+ };
+ }
+
+ public void function testGetEnvironmentIsCalled() {
+ variables.fw.getEnvironment = recordCalls;
+ variables.fwvars.getEnvironment = recordCalls;
+ variables.fw.__getEnvCalls = 0;
+ variables.fw.onRequestStart( "" );
+ assertEquals( 1, variables.fw.__getEnvCalls );
+ }
+
+ private string function recordCalls() {
+ this.__getEnvCalls += 1;
+ return "";
+ }
+
+ public void function testSetupEnvironmentIsCalled() {
+ variables.fw.setupEnvironment = recordCallsWithArg;
+ variables.fwvars.setupEnvironment = recordCallsWithArg;
+ variables.fw.__setupEnvCalls = 0;
+ variables.fw.__setupEnvArgs = [ ];
+ variables.fw.onRequestStart( "" );
+ assertEquals( 1, variables.fw.__setupEnvCalls );
+ assertEquals( "", variables.fw.__setupEnvArgs[ 1 ] );
+ }
+
+ public void function testSetupEnvironmentIsCalledWithEnv() {
+ variables.fw.getEnvironment = returnTierNoMatch;
+ variables.fwvars.getEnvironment = returnTierNoMatch;
+ variables.fw.setupEnvironment = recordCallsWithArg;
+ variables.fwvars.setupEnvironment = recordCallsWithArg;
+ variables.fw.__setupEnvCalls = 0;
+ variables.fw.__setupEnvArgs = [ ];
+ variables.fw.onRequestStart( "" );
+ assertEquals( 1, variables.fw.__setupEnvCalls );
+ assertEquals( "I do not match any tier", variables.fw.__setupEnvArgs[ 1 ] );
+ }
+
+ private void function recordCallsWithArg( string env ) {
+ this.__setupEnvCalls += 1;
+ arrayAppend( this.__setupEnvArgs, env );
+ }
+
+ public void function testDefault() {
+ variables.fw.onRequestStart( "" );
+ assertFalse( variables.fwcfg.reloadApplicationOnEveryRequest, "Reload should be default: false" );
+ assertFalse( structKeyExists( variables.fwcfg, "oneNewOption" ), "OneNewOption should not have been added" );
+ assertFalse( structKeyExists( variables.fwcfg, "useSSL" ), "UseSSL should not have been added" );
+ }
+
+ public void function testTierOnlyNoMatch() {
+ variables.fw.getEnvironment = returnTierNoMatch;
+ variables.fwvars.getEnvironment = returnTierNoMatch;
+ variables.fw.onRequestStart( "" );
+ assertFalse( variables.fwcfg.reloadApplicationOnEveryRequest, "Reload should be default: false" );
+ assertFalse( structKeyExists( variables.fwcfg, "oneNewOption" ), "OneNewOption should not have been added" );
+ assertFalse( structKeyExists( variables.fwcfg, "useSSL" ), "UseSSL should not have been added" );
+ }
+
+ private string function returnTierNoMatch() {
+ return "I do not match any tier";
+ }
+
+ public void function testTierOnlyDev() {
+ variables.fw.getEnvironment = returnTierDev;
+ variables.fwvars.getEnvironment = returnTierDev;
+ variables.fw.onRequestStart( "" );
+ assertTrue( variables.fwcfg.reloadApplicationOnEveryRequest, "Reload should be dev: true" );
+ assertFalse( structKeyExists( variables.fwcfg, "oneNewOption" ), "OneNewOption should not have been added" );
+ assertFalse( structKeyExists( variables.fwcfg, "useSSL" ), "UseSSL should not have been added" );
+ }
+
+ private string function returnTierDev() {
+ return "dev";
+ }
+
+ public void function testTierDevOne() {
+ variables.fw.getEnvironment = returnTierDevOne;
+ variables.fwvars.getEnvironment = returnTierDevOne;
+ variables.fw.onRequestStart( "" );
+ assertTrue( variables.fwcfg.reloadApplicationOnEveryRequest, "Reload should be dev: true" );
+ assertTrue( structKeyExists( variables.fwcfg, "oneNewOption" ), "OneNewOption should be present" );
+ assertFalse( structKeyExists( variables.fwcfg, "useSSL" ), "UseSSL should not have been added" );
+ }
+
+ private string function returnTierDevOne() {
+ return "dev-one";
+ }
+
+ public void function testTierDevTwo() {
+ variables.fw.getEnvironment = returnTierDevTwo;
+ variables.fwvars.getEnvironment = returnTierDevTwo;
+ variables.fw.onRequestStart( "" );
+ assertFalse( variables.fwcfg.reloadApplicationOnEveryRequest, "Reload should be dev-one: false (dev-two overrides dev)" );
+ assertFalse( structKeyExists( variables.fwcfg, "oneNewOption" ), "OneNewOption should not have been added" );
+ assertFalse( structKeyExists( variables.fwcfg, "useSSL" ), "UseSSL should not have been added" );
+ }
+
+ private string function returnTierDevTwo() {
+ return "dev-two";
+ }
+
+ public void function testTierDevNone() {
+ variables.fw.getEnvironment = returnTierDevNone;
+ variables.fwvars.getEnvironment = returnTierDevNone;
+ variables.fw.onRequestStart( "" );
+ assertTrue( variables.fwcfg.reloadApplicationOnEveryRequest, "Reload should be dev: true (dev-none introduces no override)" );
+ assertFalse( structKeyExists( variables.fwcfg, "oneNewOption" ), "OneNewOption should not have been added" );
+ assertFalse( structKeyExists( variables.fwcfg, "useSSL" ), "UseSSL should not have been added" );
+ }
+
+ private string function returnTierDevNone() {
+ return "dev-none";
+ }
+
+ public void function testTierProdOnly() {
+ variables.fw.getEnvironment = returnTierProd;
+ variables.fwvars.getEnvironment = returnTierProd;
+ variables.fw.onRequestStart( "" );
+ assertFalse( variables.fwcfg.reloadApplicationOnEveryRequest, "Reload should be prod: false" );
+ assertFalse( structKeyExists( variables.fwcfg, "oneNewOption" ), "OneNewOption should not have been added" );
+ assertTrue( structKeyExists( variables.fwcfg, "useSSL" ), "UseSSL should be present" );
+ assertTrue( variables.fwcfg.useSSL, "UseSSL should be true" );
+ }
+
+ private string function returnTierProd() {
+ return "prod";
+ }
+
+ public void function testTierProdPlus() {
+ variables.fw.getEnvironment = returnTierProdPlus;
+ variables.fwvars.getEnvironment = returnTierProdPlus;
+ variables.fw.onRequestStart( "" );
+ assertFalse( variables.fwcfg.reloadApplicationOnEveryRequest, "Reload should be prod: false" );
+ assertFalse( structKeyExists( variables.fwcfg, "oneNewOption" ), "OneNewOption should not have been added" );
+ assertTrue( structKeyExists( variables.fwcfg, "useSSL" ), "UseSSL should be present" );
+ assertTrue( variables.fwcfg.useSSL, "UseSSL should be true" );
+ }
+
+ private string function returnTierProdPlus() {
+ return "prod-plus";
+ }
+
+ public void function testHostname() {
+ // just tests we get a non-empty string
+ assertNotEquals( "", variables.fw.getHostname() );
+ }
+
+}
diff --git a/tests/frameworkErrorTest.cfc b/tests/frameworkErrorTest.cfc
new file mode 100644
index 00000000..da41713c
--- /dev/null
+++ b/tests/frameworkErrorTest.cfc
@@ -0,0 +1,52 @@
+component extends="mxunit.framework.TestCase" {
+
+ public void function setUp() {
+ variables.fw = new org.corfield.framework();
+ request.failureCount = 0;
+ request.outputContent = "";
+ injectMethod(variables.fw, this, "exceptionCapture", "dumpException");
+ }
+
+ /**
+ * Test with initialised framework - ensure error handler tries to render the main.error view
+ */
+ public void function testError()
+ {
+ var exception = {
+ type = "Testing",
+ message = "Testing",
+ detail = "Detail"
+ };
+ var event = "Test Event";
+ variables.fw.onApplicationStart();
+ savecontent variable="output" {
+ variables.fw.onError(exception, event);
+ };
+ assertEquals(request.action, "main.error");
+ assertTrue(output contains "Unable to find a view for 'main.error' action.");
+ }
+
+ /**
+ * Test with un-initialised framework - ensure internal error is not as prominent
+ */
+ public void function testEarlyError()
+ {
+ var exception = {
+ type = "Testing",
+ message = "Testing",
+ detail = "Detail"
+ };
+ var event = "";
+ savecontent variable="output" {
+ variables.fw.onError(exception, event);
+ }
+ assertFalse(output CONTAINS "Element FRAMEWORK.USINGSUBSYSTEMS is undefined in VARIABLES", "Didn't expect failure in early exception");
+ assertTrue(output CONTAINS "Exception occured before FW/1 was initialized", "Expected message about early exception");
+ }
+
+ private void function exceptionCapture( any exception)
+ {
+ writeLog(text="Exception: #exception.message#");
+ request.capturedException = arguments.exception;
+ }
+}
\ No newline at end of file
diff --git a/tests/frameworkPopulateTest.cfc b/tests/frameworkPopulateTest.cfc
new file mode 100644
index 00000000..aa4917c3
--- /dev/null
+++ b/tests/frameworkPopulateTest.cfc
@@ -0,0 +1,164 @@
+component extends="mxunit.framework.TestCase" {
+
+ public void function setUp() {
+ variables.fw = new org.corfield.framework();
+ clearFW1MetaData();
+ }
+
+ public void function testPopulateFlatComponent() {
+ var user = new stubs.userOneLevel();
+ request.context = getOneLevelRC();
+
+ variables.fw.populate( user );
+
+ assertEquals( request.context.username,user.getUserName() );
+ assertEquals( request.context.firstName,user.getFirstName() );
+ assertEquals( request.context.lastName,user.getLastName() );
+ assertEquals( request.context.isActive,user.getIsActive() );
+ }
+
+ public void function testPopulateFlatComponentWithKeys() {
+ var user = new stubs.userOneLevel();
+ request.context = getOneLevelRC();
+
+ variables.fw.populate( cfc=user, keys="username,firstname", deep=true );
+
+ assertEquals( request.context.username, user.getUserName() );
+ assertEquals( request.context.firstName, user.getFirstName() );
+ assertEquals( "", user.getLastName() );
+ assertEquals( false, user.getIsActive() );
+ }
+
+ public void function testPopulateChildComponentWithKeys() {
+ var user = new stubs.userTwoLevel();
+ request.context = getTwoLevelRC();
+
+ variables.fw.populate( cfc=user, keys="contact.firstName,username", deep=true );
+
+ assertEquals( request.context.username, user.getUserName() );
+ assertEquals( request.context[ "contact.firstName" ], user.getContact().getFirstName() );
+ assertEquals( "", user.getContact().getLastName() );
+ }
+
+ public void function testPopulateChildComponentWithTrustKeys() {
+ var user = new stubs.userTwoLevel();
+ request.context = getTwoLevelRC();
+
+ variables.fw.populate( cfc=user, trustKeys=true );
+
+ assertEquals( request.context.username,user.getUserName() );
+ assertEquals( request.context[ "contact.firstName" ], user.getContact().getFirstName() );
+ assertEquals( request.context[ "contact.lastName" ], user.getContact().getLastName() );
+ }
+
+ public void function testComponentWithSingleChild() {
+ var user = new stubs.userTwoLevel();
+ request.context = getTwoLevelRC();
+
+ variables.fw.populate( cfc=user, deep=true );
+
+ assertEquals( request.context.username,user.getUserName() );
+ assertEquals( request.context[ "contact.firstName" ], user.getContact().getFirstName() );
+ assertEquals( request.context[ "contact.lastName" ], user.getContact().getLastName() );
+ assertEquals( request.context[ "contact.dateCreated" ], user.getContact().getDateCreated() );
+ }
+
+ public void function testComponentWithSingleChildAndDeepFalse() {
+ var user = new stubs.userTwoLevel();
+ request.context = getTwoLevelRC();
+
+ variables.fw.populate( cfc=user );
+
+ assertEquals( request.context.username,user.getUserName() );
+ assertEquals( "",user.getContact().getFirstName() );
+ assertEquals( "",user.getContact().getLastName() );
+ assertEquals( true,user.getIsActive() );
+ }
+
+ public void function testComponentWithManyChildren() {
+ var user = new stubs.userThreeLevel();
+ request.context = getThreeLevelRC();
+
+ variables.fw.populate(cfc=user,deep=true);
+
+ assertEquals( request.context.username, user.getUserName() );
+ assertEquals( request.context[ "contact.firstName" ], user.getContact().getFirstName() );
+ assertEquals( request.context[ "contact.lastName" ], user.getContact().getLastName() );
+ assertEquals( request.context.isActive, user.getIsActive() );
+ assertEquals( request.context[ "contact.address.line1" ], user.getContact().getAddress().GetLine1() );
+ assertEquals( request.context[ "contact.address.line2" ], user.getContact().getAddress().GetLine2() );
+ assertEquals( request.context[ "contact.address.zipCode" ], user.getContact().getAddress().GetZipCode() );
+ }
+
+ public void function testComponentWithManyChildrenAndTrustKeys() {
+ var user = new stubs.userThreeLevel();
+ request.context = getThreeLevelRC();
+
+ variables.fw.populate( cfc=user, deep=true, trustKeys=true );
+
+ assertEquals( request.context.username, user.getUserName() );
+ assertEquals( request.context[ "contact.firstName"], user.getContact().getFirstName() );
+ assertEquals( request.context[ "contact.lastName"], user.getContact().getLastName() );
+ assertEquals( request.context.isActive, user.getIsActive() );
+ assertEquals( request.context[ "contact.address.line1"], user.getContact().getAddress().GetLine1() );
+ assertEquals( request.context[ "contact.address.line2"], user.getContact().getAddress().GetLine2() );
+ assertEquals( request.context[ "contact.address.zipCode"], user.getContact().getAddress().GetZipCode() );
+ }
+
+ public void function testComponentWithManyChildrenPassInKeys() {
+ var user = new stubs.userThreeLevel();
+ request.context = getThreeLevelRC();
+
+ variables.fw.populate( cfc=user, deep=true, keys = "contact.firstName,contact.address.line1,username" );
+
+ assertEquals( request.context.username, user.getUserName() );
+ assertEquals( request.context[ "contact.firstName" ], user.getContact().getFirstName() );
+ assertEquals( "", user.getContact().getLastName() );
+ assertEquals( false, user.getIsActive() );
+ assertEquals( request.context[ "contact.address.line1" ], user.getContact().getAddress().GetLine1() );
+ assertEquals( "", user.getContact().getAddress().GetLine2() );
+ assertEquals( "", user.getContact().getAddress().GetZipCode() );
+ }
+
+ private Struct function getOneLevelRC()
+ output=false {
+ return { username = "foobar", firstName="Homer", lastName="Simpson", isActive=true };
+ }
+
+ private Struct function getTwoLevelRC()
+ output=false {
+ return { username = "foobar", "contact.firstName" = "Homer", "contact.lastName" = "Simpson", isActive = true, "contact.dateCreated" = "02/29/2012" };
+ }
+
+ private Struct function getThreeLevelRC()
+ output=false {
+ return {
+ username = "foobar",
+ "contact.firstName" = "Homer",
+ "contact.lastName" = "Simpson",
+ "contact.dateCreated" = "02/29/2012",
+ isActive = true,
+ "contact.address.line1" = "123 Fake Street",
+ "contact.address.line2" = "Apt 12",
+ "contact.address.zipCode" = "54232"
+ };
+ }
+
+ private void function clearFW1MetaData()
+ output=false hint=""{
+ var cfcs = {};
+
+ cfcs[ "stubs.Address" ] = getMetaData( new stubs.Address() );
+
+ cfcs[ "stubs.Contact" ] = getMetaData( new stubs.Contact() );
+ cfcs[ "stubs.UserOneLevel" ] = getMetaData( new stubs.UserOneLevel() );
+ cfcs[ "stubs.UserTwoLevel" ] = getMetaData( new stubs.UserTwoLevel() );
+ cfcs[ "stubs.UserThreeLevel" ] = getMetaData( new stubs.UserThreeLevel() );
+
+ for(cfc in cfcs){
+ if ( structKeyExists( cfcs[ cfc ], '__fw1_setters' ) ) {
+ structDelete( cfcs[ cfc ], "__fw1_setters" );
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/frameworkRouteTest.cfc b/tests/frameworkRouteTest.cfc
new file mode 100644
index 00000000..053dab6e
--- /dev/null
+++ b/tests/frameworkRouteTest.cfc
@@ -0,0 +1,85 @@
+component extends="tests.InjectableTest" {
+
+ public void function setUp() {
+ variables.fw = new org.corfield.framework();
+ // doesn't work on Railo:
+ // makePublic(variables.fw, "processRouteMatch");
+ // this works on both Railo and ACF:
+ variables.fw.processRouteMatch = getVariablesScope(variables.fw).processRouteMatch;
+ }
+
+ public void function testRouteMatchBasics()
+ {
+ var match = variables.fw.processRouteMatch("/test", "routed", "/test");
+ assertTrue(match.matched);
+ assertEquals("/test/(.*)", match.pattern);
+ assertEquals("routed/\1", match.target);
+
+ match = variables.fw.processRouteMatch("/test2/:id", "default.main?id=:id", "/test2/5");
+ assertTrue(match.matched);
+ assertEquals("/test2/([^/]*)/(.*)", match.pattern);
+ assertEquals("default.main?id=\1/\2", match.target);
+
+ match = variables.fw.processRouteMatch("/test2/:id", "default.main?id=:id", "/test2");
+ assertFalse(match.matched);
+
+ match = variables.fw.processRouteMatch("/test/:foo/bar/:baz", "default.main?foo=:foo&baz=:baz", "/test/quux/bar/fnarf");
+ assertTrue(match.matched);
+ assertEquals("/test/([^/]*)/bar/([^/]*)/(.*)", match.pattern);
+ assertEquals("default.main?foo=\1&baz=\2/\3", match.target);
+ }
+
+ public void function testRouteMatchRegex()
+ {
+ match = variables.fw.processRouteMatch("/test2/:id", "default.main?id=:id", "/test2/5/people");
+ assertTrue(match.matched);
+
+ match = variables.fw.processRouteMatch("/(blog|forum|forums)/:action/", "/forum::action/", "/blog/post");
+ assertTrue(match.matched);
+ assertEquals("/forum:post/", rereplace( match.path, match.pattern, match.target ));
+
+ match = variables.fw.processRouteMatch("/test2/:id/", "default.main?id=:id", "/test2/5/people");
+ assertTrue(match.matched, "/test2/:id should match /test2/5/people");
+
+ match = variables.fw.processRouteMatch("/test2/:id/$", "default.main?id=:id", "/test2/5/people");
+ assertFalse(match.matched, "/test2/:id/$ shouldn't match /test2/5/people");
+
+ match = variables.fw.processRouteMatch("/test2/(\d+)/$", "default.main/id/\1/", "/test2/5");
+ assertTrue(match.matched);
+ assertEquals("default.main/id/5/", rereplace(match.path, match.pattern, match.target));
+
+ match = variables.fw.processRouteMatch("/test2/(\d+)/$", "default.main/id/\1/", "/test2/zz/");
+ assertFalse(match.matched);
+
+ var route = "/test/(\d+)/something(\.)?(\w+)?/$";
+ var target = "default.main/id/\1/type/\3/";
+ match = variables.fw.processRouteMatch(route, target, "/test/5/something/");
+ assertTrue(match.matched, "/test/5/something/ should match");
+ assertEquals("default.main/id/5/type//", rereplace(match.path, match.pattern, match.target));
+
+ match = variables.fw.processRouteMatch(route, target, "/test/5/something.html/");
+ assertTrue(match.matched);
+ assertEquals("default.main/id/5/type/html/", rereplace(match.path, match.pattern, match.target));
+ }
+
+ public void function testRouteMatchMethod()
+ {
+ match = variables.fw.processRouteMatch("$GET/test/:id", "default.main?id=:id", "/test/5");
+ assertTrue(match.matched);
+
+ match = variables.fw.processRouteMatch("$POST/test/:id", "default.main?id=:id", "/test/5");
+ assertFalse(match.matched);
+ }
+
+ public void function testRouteMatchRedirect()
+ {
+ match = variables.fw.processRouteMatch("/test/:id", "default.main?id=:id", "/test/5");
+ assertTrue(match.matched);
+ assertFalse(match.redirect);
+
+ match = variables.fw.processRouteMatch("/test/:id", "302:default.main?id=:id", "/test/5");
+ assertTrue(match.matched);
+ assertTrue(match.redirect);
+ assertEquals(302, match.statusCode);
+ }
+}
diff --git a/tests/frameworkTestSuite.cfm b/tests/frameworkTestSuite.cfm
new file mode 100644
index 00000000..69e96025
--- /dev/null
+++ b/tests/frameworkTestSuite.cfm
@@ -0,0 +1,11 @@
+
+testSuite = createObject("component","mxunit.framework.TestSuite").TestSuite();
+testSuite.addAll("tests.frameworkPopulateTest");
+testSuite.addAll("tests.frameworkErrorTest");
+testSuite.addAll("tests.frameworkRouteTest");
+testSuite.addAll("tests.frameworkEnvTest");
+testSuite.addAll("tests.onMissingViewLayout");
+testSuite.addAll("tests.onSessionStartBuildURL");
+results = testSuite.run();
+writeOutput(results.getResultsOutput('html'));
+
diff --git a/tests/omv/layouts/main/default.cfm b/tests/omv/layouts/main/default.cfm
new file mode 100644
index 00000000..3cc31863
--- /dev/null
+++ b/tests/omv/layouts/main/default.cfm
@@ -0,0 +1 @@
+DEFAULT#body#
diff --git a/tests/omv/layouts/main/two.cfm b/tests/omv/layouts/main/two.cfm
new file mode 100644
index 00000000..35e390d3
--- /dev/null
+++ b/tests/omv/layouts/main/two.cfm
@@ -0,0 +1 @@
+TWO#body#
diff --git a/tests/omv/views/main/test.cfm b/tests/omv/views/main/test.cfm
new file mode 100644
index 00000000..2a02d41c
--- /dev/null
+++ b/tests/omv/views/main/test.cfm
@@ -0,0 +1 @@
+TEST
diff --git a/tests/onMissingViewLayout.cfc b/tests/onMissingViewLayout.cfc
new file mode 100644
index 00000000..ae350026
--- /dev/null
+++ b/tests/onMissingViewLayout.cfc
@@ -0,0 +1,29 @@
+component extends="tests.InjectableTest" {
+
+ public void function setUp() {
+ structClear( request );
+ variables.fw = new org.corfield.framework();
+ variables.fwvars = getVariablesScope( variables.fw );
+ variables.fwvars.framework = {
+ base = "/tests/omv"
+ };
+ }
+
+ public void function testSetLayout() {
+ // should be able to run setLayout() in onMissingView() and have it choose the layout
+ variables.fw.onMissingView = selectLayoutTwo;
+ variables.fwvars.onMissingView = selectLayoutTwo;
+ variables.fw.onRequestStart( "" );
+ var output = "";
+ savecontent variable="output" {
+ variables.fw.onRequest( "" );
+ }
+ assertEquals( "TWOTEST", trim( output ) );
+ }
+
+ private string function selectLayoutTwo( struct rc ) {
+ setLayout( "main.two" );
+ return view( "main/test" );
+ }
+
+}
diff --git a/tests/onSessionStartBuildURL.cfc b/tests/onSessionStartBuildURL.cfc
new file mode 100644
index 00000000..600e387a
--- /dev/null
+++ b/tests/onSessionStartBuildURL.cfc
@@ -0,0 +1,27 @@
+component extends="tests.InjectableTest" {
+
+ public void function setUp() {
+ structClear( request );
+ variables.fw = new org.corfield.framework();
+ variables.fwvars = getVariablesScope( variables.fw );
+ variables.fwvars.framework = {
+ generateSES = true,
+ SESOmitIndex = true
+ };
+ }
+
+ public void function testBuildURL() {
+ // ensure SES URL gets generated in onSessionStart:
+ variables.fw.setupSession = buildSESURL;
+ variables.fwvars.setupSession = buildSESURL;
+ variables.fw.__url = "";
+ variables.fw.onSessionStart();
+ var expected = "/foo/test/bar/1";
+ assertEquals( expected, right( variables.fw.__url, len( expected ) ) );
+ }
+
+ private void function buildSESURL() {
+ this.__url = buildURL( action = "foo.test", queryString = "bar=1" );
+ }
+
+}
diff --git a/tests/stubs/Address.cfc b/tests/stubs/Address.cfc
new file mode 100644
index 00000000..1545c194
--- /dev/null
+++ b/tests/stubs/Address.cfc
@@ -0,0 +1,15 @@
+component accessors = true{
+
+ property name = line1 getters = true setters = true type = string;
+
+ property name = line2 getters = true setters = true type = string;
+
+ property name = zipCode getters = true setters = true type = string;
+
+ public void function init()
+ output=false hint="constructor"{
+ variables.line1 = "";
+ variables.line2 = "";
+ variables.zipCode = "";
+ }
+}
\ No newline at end of file
diff --git a/tests/stubs/Contact.cfc b/tests/stubs/Contact.cfc
new file mode 100644
index 00000000..b2243c00
--- /dev/null
+++ b/tests/stubs/Contact.cfc
@@ -0,0 +1,16 @@
+component accessors = true {
+
+ property name = firstName getters = true setters = true type = string;
+ property name = lastName getters = true setters = true type = string;
+ property name = dateCreated getters = true setters = true type = date;
+ property name = address getters = true setters = true type = stubs.Address;
+
+ public void function init()
+ output = false hint = "constructor" {
+ variables.firstName = "";
+ variables.lastName = "";
+
+ variables.Address = new Address();
+ //intentionally not initing date created
+ }
+}
\ No newline at end of file
diff --git a/tests/stubs/UserOneLevel.cfc b/tests/stubs/UserOneLevel.cfc
new file mode 100644
index 00000000..cf5e800c
--- /dev/null
+++ b/tests/stubs/UserOneLevel.cfc
@@ -0,0 +1,16 @@
+component accessors = true {
+
+
+ property name = username getters = true setters = true type = string;
+ property name = firstName getters = true setters = true type = string;
+ property name = lastName getters = true setters = true type = string;
+ property name = isActive getters = true setters = true type = boolean;
+
+ public void function init()
+ output = false hint = "constructor" {
+ variables.username = "";
+ variables.firstName = "";
+ variables.lastName = "";
+ variables.isActive = false;
+ }
+}
\ No newline at end of file
diff --git a/tests/stubs/UserThreeLevel.cfc b/tests/stubs/UserThreeLevel.cfc
new file mode 100644
index 00000000..c44c53d5
--- /dev/null
+++ b/tests/stubs/UserThreeLevel.cfc
@@ -0,0 +1,14 @@
+component accessors = true {
+
+ property name = username getters = true setters = true type = string;
+ property name = isActive getters = true setters = true type = boolean;
+ property name = contact getters = true setters = true type = stubs.Contact;
+
+
+ public void function init()
+ output = false hint = "constructor" {
+ variables.username = "";
+ variables.Contact = new Contact();
+ variables.isActive = false;
+ }
+}
\ No newline at end of file
diff --git a/tests/stubs/UserTwoLevel.cfc b/tests/stubs/UserTwoLevel.cfc
new file mode 100644
index 00000000..3bf0b425
--- /dev/null
+++ b/tests/stubs/UserTwoLevel.cfc
@@ -0,0 +1,13 @@
+component accessors = true {
+
+ property name = username getters = true setters = true type = string;
+ property name = contact getters = true setters = true type = stubs.Contact;
+ property name = isActive getters = true setters = true type = boolean;
+
+
+ public void function init()
+ output = false hint = "constructor" {
+ variables.username = "";
+ variables.Contact = new Contact();
+ }
+}
\ No newline at end of file