diff --git a/.travis.yml b/.travis.yml index 21bf0d45..702941e6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,12 @@ language: java - sudo: required +dist: bionic env: matrix: - - ENGINE=lucee@4.5 - ENGINE=lucee@5 + - ENGINE=lucee@4.5 + - ENGINE=adobe@2018 - ENGINE=adobe@2016 - ENGINE=adobe@11 - ENGINE=adobe@10 diff --git a/README.md b/README.md index 30ccc2ef..e0e1ad4f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# FW/1 (Framework One) [![Build Status](https://travis-ci.org/framework-one/fw1.png)](https://travis-ci.org/framework-one/fw1) [![Stories in Ready](https://badge.waffle.io/framework-one/fw1.png?label=ready&title=Ready)](http://waffle.io/framework-one/fw1) [![Join the chat at https://gitter.im/framework-one/fw1](https://badges.gitter.im/framework-one/fw1.svg)](https://gitter.im/framework-one/fw1?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# FW/1 (Framework One) [![Build Status](https://travis-ci.org/framework-one/fw1.png)](https://travis-ci.org/framework-one/fw1) [![Join the chat at https://gitter.im/framework-one/fw1](https://badges.gitter.im/framework-one/fw1.svg)](https://gitter.im/framework-one/fw1?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 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 diff --git a/box.json b/box.json index 20c0e1dd..6bcf7115 100644 --- a/box.json +++ b/box.json @@ -1,9 +1,9 @@ { "name":"Framework One", "slug":"fw1", - "version":"4.2.0", + "version":"4.3.0", "author":"Sean Corfield, Marcin Szczepanski, Ryan Cogswell", - "location":"framework-one/fw1#master", + "location":"framework-one/fw1#develop", "createPackageDirectory":true, "packageDirectory":"framework", "Homepage":"http://framework-one.github.io/", @@ -16,7 +16,7 @@ "shortDescription":"FW/1 - Framework One - is a family of small, lightweight, convention-over-configuration frameworks, primarily for CFML.", "description":"FW/1 - Framework One - is a family of small, lightweight, convention-over-configuration frameworks, primarily for CFML. FW/1 itself provides MVC, DI/1 provides dependency injection (a.k.a. inversion of control), and AOP/1 provides aspect-oriented programming features on top of DI/1.", "instructions":"http://framework-one.github.io/documentation/", - "changelog":"https://github.com/framework-one/fw1/commits/master", + "changelog":"https://github.com/framework-one/fw1/commits/develop", "type":"mvc", "keywords":[ "fw1", @@ -55,7 +55,7 @@ ], "devDependencies":{ - "testbox":"^2.5.0+107" + "testbox":"2.5.0+107" }, "installPaths":{ "testbox":"testbox" diff --git a/framework/Application.cfc b/framework/Application.cfc index ca615b05..4a88ffdb 100644 --- a/framework/Application.cfc +++ b/framework/Application.cfc @@ -1,5 +1,5 @@ component { - // Version: FW/1 4.2.0 + // Version: FW/1 4.3.0 // copy this to your application root to use as your Application.cfc // or incorporate the logic below into your existing Application.cfc diff --git a/framework/MyApplication.cfc b/framework/MyApplication.cfc index d32f1cbc..460c5198 100644 --- a/framework/MyApplication.cfc +++ b/framework/MyApplication.cfc @@ -1,5 +1,5 @@ component extends="framework.one" { - // Version: FW/1 4.2.0 + // Version: FW/1 4.3.0-SNAPSHOT // if you need to provide extension points, copy this to // your web root, next to your Application.cfc, and add diff --git a/framework/WireBoxAdapter.cfc b/framework/WireBoxAdapter.cfc index 4738a5aa..10ba0ab7 100644 --- a/framework/WireBoxAdapter.cfc +++ b/framework/WireBoxAdapter.cfc @@ -1,5 +1,5 @@ component extends="wirebox.system.ioc.Injector" { - variables._fw1_version = "4.2.0"; + variables._fw1_version = "4.3.0"; /* Copyright (c) 2010-2018, Sean Corfield diff --git a/framework/aop.cfc b/framework/aop.cfc index b3910da8..33e75afd 100644 --- a/framework/aop.cfc +++ b/framework/aop.cfc @@ -1,5 +1,5 @@ component extends="framework.ioc" { - variables._fw1_version = "4.2.0"; + variables._fw1_version = "4.3.0"; variables._aop1_version = variables._fw1_version; /* Copyright (c) 2013-2018, Mark Drew, Sean Corfield, Daniel Budde @@ -119,7 +119,7 @@ component extends="framework.ioc" { { // build the interceptor array: var beanName = listLast(arguments.dottedPath, "."); - var beanNames = getAliases(beanName); + var beanNames = getAliases(arguments.dottedPath); var beanTypes = ""; var interceptDefinition = ""; var interceptedBeanName = ""; @@ -128,6 +128,8 @@ component extends="framework.ioc" { arrayPrepend(beanNames, beanName); + // Removing duplicate beanNames + beanNames = listToArray(listRemoveDuplicates(arrayToList(beanNames),",",true) ); // Grab all name based interceptors that match. for (interceptedBeanName in beanNames) @@ -185,7 +187,7 @@ component extends="framework.ioc" { var interceptedBeanName = ""; var interceptorDefinition = {}; var beanName = listLast(arguments.dottedPath, "."); - var beanNames = getAliases(beanName); + var beanNames = getAliases(arguments.dottedPath); var beanTypes = ""; @@ -234,29 +236,21 @@ component extends="framework.ioc" { } - /** Finds all aliases for the given beanName. */ - private array function getAliases(string beanName) + /** Finds all aliases for the given dottedPath. */ + private array function getAliases(string dottedPath) { var aliases = []; var beanData = ""; var key = ""; - - if (structKeyExists(variables.beanInfo, arguments.beanName)) + for (key in variables.beanInfo) { - beanData = variables.beanInfo[arguments.beanName]; - - for (key in variables.beanInfo) + // Same cfc dotted path, must be an alias. + if ( + structKeyExists(variables.beanInfo[key], "cfc") && + variables.beanInfo[key].cfc == arguments.dottedPath) { - // Same cfc dotted path, must be an alias. - if ( - key != arguments.beanName && - structKeyExists(variables.beanInfo[key], "cfc") && - structKeyExists(variables.beanInfo[arguments.beanName], "cfc") && - variables.beanInfo[key].cfc == variables.beanInfo[arguments.beanName].cfc) - { - arrayAppend(aliases, key); - } + arrayAppend(aliases, key); } } diff --git a/framework/beanProxy.cfc b/framework/beanProxy.cfc index 00cffb56..dce63b25 100644 --- a/framework/beanProxy.cfc +++ b/framework/beanProxy.cfc @@ -1,5 +1,5 @@ component { - variables._fw1_version = "4.2.0"; + variables._fw1_version = "4.3.0"; variables._aop1_version = variables._fw1_version; /* Copyright (c) 2013-2018, Mark Drew, Sean Corfield, Daniel Budde diff --git a/framework/facade.cfc b/framework/facade.cfc index a2228a3e..795c2d43 100644 --- a/framework/facade.cfc +++ b/framework/facade.cfc @@ -1,5 +1,5 @@ component { - variables._fw1_version = "4.2.0"; + variables._fw1_version = "4.3.0"; /* Copyright (c) 2016-2018, Sean Corfield diff --git a/framework/ioc.cfc b/framework/ioc.cfc index 767559e9..989460e1 100644 --- a/framework/ioc.cfc +++ b/framework/ioc.cfc @@ -1,5 +1,5 @@ component { - variables._fw1_version = "4.2.0"; + variables._fw1_version = "4.3.0"; variables._di1_version = variables._fw1_version; /* Copyright (c) 2010-2018, Sean Corfield @@ -291,15 +291,17 @@ component { } - // 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 ) { + /* + * @hint Given a bean (by name, by type or by value), call the named setters with the specified property values + * @ignoreMissing When set verify that the setter to be called exists and skip if missing, otherwise throws an error + */ + public any function injectProperties( any bean, struct properties, boolean ignoreMissing=false ) { if ( isSimpleValue( bean ) ) { if ( containsBean( bean ) ) bean = getBean( bean ); else bean = construct( bean ); } for ( var property in properties ) { - if ( !isNull( properties[ property ] ) ) { + if ( !isNull( properties[ property ] ) && (!ignoreMissing || structKeyExists( bean, "set#property#" ) ) ){ var args = { }; args[ property ] = properties[ property ]; invoke( bean, "set#property#", args ); @@ -533,6 +535,7 @@ component { } catch ( any e ) { // assume bad path - ignore it, cfcs is empty list } + local.beansWithDuplicates = ""; for ( var cfcOSPath in cfcs ) { var cfcPath = replace( cfcOSPath, chr(92), '/', 'all' ); // watch out for excluded paths: @@ -559,18 +562,26 @@ component { if ( structKeyExists( metadata.metadata, "type" ) && metadata.metadata.type == "interface" ) { continue; } - if ( structKeyExists( variables.beanInfo, beanName ) ) { - if ( variables.config.omitDirectoryAliases ) { - throw '#beanName# is not unique (and omitDirectoryAliases is true)'; + + if ( variables.config.omitDirectoryAliases ) { + if ( structKeyExists( variables.beanInfo, beanName ) ) { + throw '#beanName# is not unique'; } - structDelete( variables.beanInfo, beanName ); - variables.beanInfo[ beanName & singleDir ] = metadata; - } else { variables.beanInfo[ beanName ] = metadata; - if ( !variables.config.omitDirectoryAliases ) { - variables.beanInfo[ beanName & singleDir ] = metadata; + } else { + if ( listFindNoCase(local.beansWithDuplicates, beanName) ) {} + else if ( structKeyExists( variables.beanInfo, beanName ) ) { + structDelete( variables.beanInfo, beanName ); + local.beansWithDuplicates = listAppend(local.beansWithDuplicates, beanName); + } else { + variables.beanInfo[ beanName ] = metadata; + } + if ( structKeyExists( variables.beanInfo, beanName & singleDir ) ) { + throw '#beanName & singleDir# is not unique'; } + variables.beanInfo[ beanName & singleDir ] = metadata; } + } catch ( any e ) { // wrap the exception so we can add bean name for debugging // this trades off any stack trace information for the bean name but diff --git a/framework/methodProxy.cfc b/framework/methodProxy.cfc index 905d362a..4a84abb6 100644 --- a/framework/methodProxy.cfc +++ b/framework/methodProxy.cfc @@ -1,5 +1,5 @@ component { - variables._fw1_version = "4.2.0"; + variables._fw1_version = "4.3.0"; /* Copyright (c) 2018, Sean Corfield diff --git a/framework/one.cfc b/framework/one.cfc index f2354f5d..04c51f07 100644 --- a/framework/one.cfc +++ b/framework/one.cfc @@ -1,5 +1,5 @@ component { - variables._fw1_version = "4.2.0"; + variables._fw1_version = "4.3.0"; /* Copyright (c) 2009-2018, Sean Corfield, Marcin Szczepanski, Ryan Cogswell @@ -520,7 +520,6 @@ component { return listFirst( getSectionAndItem( action ), '.' ); } - /* * return the action without the subsystem */ @@ -592,6 +591,13 @@ component { return { }; } + /* + * return the subsytem and section part of the action + */ + public string function getSubsystemSection( string action = request.action ) { + return listFirst( getSubsystemSectionAndItem( action ), '.' ); + } + /* * return an action with all applicable parts (subsystem, section, and item) specified * using defaults from the configuration or request where appropriate @@ -1825,18 +1831,21 @@ component { var controllersSlash = variables.framework.controllersFolder & '/'; var controllersDot = variables.framework.controllersFolder & '.'; // per #310 we no longer cache the Application controller since it is new on each request - if ( !structKeyExists( cache.controllers, componentKey ) || section == variables.magicApplicationController ) { + if ( section == variables.magicApplicationController ) { + if ( hasDefaultBeanFactory() ) { + autowire( this, getDefaultBeanFactory() ); + } + return this; + } + if ( !structKeyExists( cache.controllers, componentKey ) ) { lock name="fw1_#application.applicationName#_#variables.framework.applicationKey#_#componentKey#" type="exclusive" timeout="30" { - if ( !structKeyExists( cache.controllers, componentKey ) || section == variables.magicApplicationController ) { + if ( !structKeyExists( cache.controllers, componentKey ) ) { if ( hasSubsystemBeanFactory( subsystem ) && getSubsystemBeanFactory( subsystem ).containsBean( beanName ) ) { cfc = getSubsystemBeanFactory( subsystem ).getBean( beanName ); } else if ( !usingSubsystems() && hasDefaultBeanFactory() && getDefaultBeanFactory().containsBean( beanName ) ) { cfc = getDefaultBeanFactory().getBean( beanName ); } else { - if ( section == variables.magicApplicationController ) { - // treat this (Application.cfc) as a controller: - cfc = this; - } else if ( cachedFileExists( cfcFilePath( request.cfcbase ) & subsystemDir & controllersSlash & section & '.cfc' ) ) { + if ( cachedFileExists( cfcFilePath( request.cfcbase ) & subsystemDir & controllersSlash & section & '.cfc' ) ) { // we call createObject() rather than new so we can control initialization: if ( request.cfcbase == '' ) { cfc = createObject( 'component', subsystemDot & controllersDot & section ); @@ -1884,7 +1893,7 @@ component { var nextPreserveKey = ''; var oldKeyToPurge = ''; try { - sessionLock(function(){ + sessionLock(function() localmode = "classic" { if ( variables.framework.maxNumContextsPreserved > 1 ) { sessionDefault( '__fw1NextPreserveKey', 1 ); nextPreserveKey = sessionRead( '__fw1NextPreserveKey' ); @@ -2901,7 +2910,11 @@ component { case "text/json": try { var bodyStruct = read_json( body ); - structAppend( request.context, bodyStruct ); + if ( isStruct( bodyStruct ) ) { + structAppend( request.context, bodyStruct ); + } else { + request.context[ 'body' ] = bodyStruct; + } } catch ( any e ) { throw( type = "FW1.JSONPOST", message = "Content-Type implies JSON but could not deserialize body: " & e.message ); @@ -2979,51 +2992,53 @@ component { private void function setupSubsystemWrapper( string subsystem ) { if ( !len( subsystem ) ) return; - lock name="fw1_#application.applicationName#_#variables.framework.applicationKey#_subsysteminit_#subsystem#" type="exclusive" timeout="30" { - if ( !isSubsystemInitialized( subsystem ) ) { - getFw1App().subsystems[ subsystem ] = now(); - // Application.cfc does not get a subsystem bean factory! - if ( subsystem != variables.magicApplicationSubsystem ) { - var subsystemConfig = getSubsystemConfig( subsystem ); - var diEngine = structKeyExists( subsystemConfig, 'diEngine' ) ? subsystemConfig.diEngine : variables.framework.diEngine; - if ( diEngine == "di1" || diEngine == "aop1" ) { - // we can only reliably automate D/I engine setup for DI/1 / AOP/1 - var diLocations = structKeyExists( subsystemConfig, 'diLocations' ) ? subsystemConfig.diLocations : variables.framework.diLocations; - var locations = isSimpleValue( diLocations ) ? listToArray( diLocations ) : diLocations; - var subLocations = ""; - for ( var loc in locations ) { - var relLoc = trim( loc ); - // make a relative location: - if ( len( relLoc ) > 2 && left( relLoc, 2 ) == "./" ) { - relLoc = right( relLoc, len( relLoc ) - 2 ); - } else if ( len( relLoc ) > 1 && left( relLoc, 1 ) == "/" ) { - relLoc = right( relLoc, len( relLoc ) - 1 ); - } - if ( usingSubsystems() ) { - subLocations = listAppend( subLocations, variables.framework.base & subsystem & "/" & relLoc ); - } else { - subLocations = listAppend( subLocations, variables.framework.base & variables.framework.subsystemsFolder & "/" & subsystem & "/" & relLoc ); + if ( !isSubsystemInitialized( subsystem ) ) { + lock name="fw1_#application.applicationName#_#variables.framework.applicationKey#_subsysteminit_#subsystem#" type="exclusive" timeout="30" { + if ( !isSubsystemInitialized( subsystem ) ) { + getFw1App().subsystems[ subsystem ] = now(); + // Application.cfc does not get a subsystem bean factory! + if ( subsystem != variables.magicApplicationSubsystem ) { + var subsystemConfig = getSubsystemConfig( subsystem ); + var diEngine = structKeyExists( subsystemConfig, 'diEngine' ) ? subsystemConfig.diEngine : variables.framework.diEngine; + if ( diEngine == "di1" || diEngine == "aop1" ) { + // we can only reliably automate D/I engine setup for DI/1 / AOP/1 + var diLocations = structKeyExists( subsystemConfig, 'diLocations' ) ? subsystemConfig.diLocations : variables.framework.diLocations; + var locations = isSimpleValue( diLocations ) ? listToArray( diLocations ) : diLocations; + var subLocations = ""; + for ( var loc in locations ) { + var relLoc = trim( loc ); + // make a relative location: + if ( len( relLoc ) > 2 && left( relLoc, 2 ) == "./" ) { + relLoc = right( relLoc, len( relLoc ) - 2 ); + } else if ( len( relLoc ) > 1 && left( relLoc, 1 ) == "/" ) { + relLoc = right( relLoc, len( relLoc ) - 1 ); + } + if ( usingSubsystems() ) { + subLocations = listAppend( subLocations, variables.framework.base & subsystem & "/" & relLoc ); + } else { + subLocations = listAppend( subLocations, variables.framework.base & variables.framework.subsystemsFolder & "/" & subsystem & "/" & relLoc ); + } } - } - if ( len( sublocations ) ) { - var diComponent = structKeyExists( subsystemConfig, 'diComponent' ) ? subsystemConfig : variables.framework.diComponent; - var cfg = { }; - if ( structKeyExists( subsystemConfig, 'diConfig' ) ) { - cfg = subsystemConfig.diConfig; - } else { - cfg = structCopy( variables.framework.diConfig ); - structDelete( cfg, 'loadListener' ); + if ( len( sublocations ) ) { + var diComponent = structKeyExists( subsystemConfig, 'diComponent' ) ? subsystemConfig : variables.framework.diComponent; + var cfg = { }; + if ( structKeyExists( subsystemConfig, 'diConfig' ) ) { + cfg = subsystemConfig.diConfig; + } else { + cfg = structCopy( variables.framework.diConfig ); + structDelete( cfg, 'loadListener' ); + } + cfg.noClojure = true; + var ioc = new "#diComponent#"( subLocations, cfg ); + ioc.setParent( getDefaultBeanFactory() ); + setSubsystemBeanFactory( subsystem, ioc ); } - cfg.noClojure = true; - var ioc = new "#diComponent#"( subLocations, cfg ); - ioc.setParent( getDefaultBeanFactory() ); - setSubsystemBeanFactory( subsystem, ioc ); } } - } - internalFrameworkTrace( 'setupSubsystem() called', subsystem ); - setupSubsystem( subsystem ); + internalFrameworkTrace( 'setupSubsystem() called', subsystem ); + setupSubsystem( subsystem ); + } } } } diff --git a/tests/CombinedInterceptorsTestIssue518.cfc b/tests/CombinedInterceptorsTestIssue518.cfc new file mode 100644 index 00000000..789c8f26 --- /dev/null +++ b/tests/CombinedInterceptorsTestIssue518.cfc @@ -0,0 +1,289 @@ +component extends="mxunit.framework.TestCase" { + + function TestBeforeAroundAfterInterception() { + //Putting it all together What happens when you call all of them? + request.callstack = []; //reset + bf = new framework.aop('/tests/issue518', {}); + //add an Interceptor + + bf.intercept("ReverseService", "BeforeInterceptor"); + bf.intercept("ReverseService", "AroundInterceptor"); + bf.intercept("ReverseService", "AfterInterceptor"); + + rs = bf.getBean("ReverseService"); + result = rs.doReverse("Hello!"); + + + AssertEquals("around," & Reverse("beforeHello!") & ",around", result); + AssertEquals(4, arrayLen(request.callstack)); + AssertEquals("before,around,doReverse,after", arrayToList(request.callstack)); + } + + + function TestInitMethods() { + request.callstack = []; //reset + bf = new framework.aop('/tests/issue518', {initMethod = "configure"}); + + bf.intercept("advReverse", "BeforeInterceptor"); + + rs = bf.getBean("advReverseService"); + result = rs.doWrap("Hello!"); + + // First test does not intercept the (init, set..., or initMethod) methods. + AssertEquals(9, arrayLen(request.callstack)); + AssertEquals( "init,setStackLog,configure,before,dowrap,before,dofront,before,dorear", + arrayToList(request.callstack), + "This test shows that the (init, set..., and configure) methods are by default ignored."); + + + request.callstack = []; //reset + bf = new framework.aop('/tests/issue518', {initMethod = "configure"}); + + bf.intercept("advReverse", "BeforeInterceptor", "init,configure,setStackLog,doWrap"); + + rs = bf.getBean("advReverseService"); + result = rs.doWrap("Hello!"); + + // Explicitly intercept the (init, set..., or initMethod) methods. + AssertEquals(10, arrayLen(request.callstack)); + AssertEquals( "before,init,before,setStackLog,before,configure,before,dowrap,dofront,dorear", + arrayToList(request.callstack), + "This test shows that the (init, set..., and configure) methods can be explicitly intercepted."); + } + + + function TestInterceptOnRegex() { + request.callstack = []; //reset + bf = new framework.aop('/tests/issue518', {initMethod = "configure"}); + + //add an Interceptor + bf.intercept("/^reverse.*$/", "BeforeInterceptor"); + + ars = bf.getBean("advReverseService"); + rs = bf.getBean("reverse"); + as = bf.getBean("array"); + + + result = ars.doWrap("Hello!"); + result2 = rs.doReverse("Hello!"); + result3 = as.doListToArray("dog,cat,mouse"); + + + AssertEquals("front-Hello!-rear", result); + AssertEquals("!olleHerofeb", result2); + AssertTrue(isArray(result3)); + AssertEquals("dog,cat,mouse", arrayToList(result3)); + + AssertEquals(9, arrayLen(request.callstack)); + AssertEquals("init,setStackLog,configure,doWrap,doFront,doRear,before,doReverse,doListToArray", arrayToList(request.callstack)); + } + + + function TestInterceptOnType() { + request.callstack = []; //reset + bf = new framework.aop('/tests/issue518', {initMethod = "configure"}); + + //add an Interceptor + bf.interceptByType("string", "BeforeInterceptor", "doReverse,doForward,doWrap"); + + ars = bf.getBean("advReverseService"); + rs = bf.getBean("reverse"); + as = bf.getBean("array"); + + + result = ars.doWrap("Hello!"); + result2 = rs.doReverse("Hello!"); + result3 = as.doListToArray("dog,cat,mouse"); + + + AssertEquals("front-beforeHello!-rear", result); + AssertEquals("!olleHerofeb", result2); + AssertTrue(isArray(result3)); + AssertEquals("dog,cat,mouse", arrayToList(result3)); + + AssertEquals(10, arrayLen(request.callstack)); + AssertEquals("init,setStackLog,configure,before,doWrap,doFront,doRear,before,doReverse,doListToArray", arrayToList(request.callstack)); + } + + + function TestMultipleBeforeInterceptions() { + //Multiple Before Advisors + request.callstack = []; //reset + bf = new framework.aop('/tests/issue518', {}); + + //Need to create different Before interceptors + bf.declareBean("BeforeInterceptorA", "tests.issue518.interceptors.aop.BeforeInterceptor", true, {name = "beforeA"}); + bf.declareBean("BeforeInterceptorB", "tests.issue518.interceptors.aop.BeforeInterceptor", true, {name = "beforeB"}); + bf.declareBean("BeforeInterceptorC", "tests.issue518.interceptors.aop.BeforeInterceptor", true, {name = "beforeC"}); + + bf.intercept("ReverseService", "BeforeInterceptorA"); + bf.intercept("ReverseService", "BeforeInterceptorB"); + bf.intercept("ReverseService", "BeforeInterceptorC"); + + rs = bf.getBean("ReverseService"); + result = rs.doReverse("Hello!"); + + AssertEquals(reverse("beforebeforebeforeHello!"), result); + AssertEquals(4, arrayLen(request.callstack)); + AssertEquals("beforeA,beforeB,beforeC,doReverse", arrayToList(request.callstack)); + } + + + function TestMultipleAfterInterceptors() { + //Multiple After Advisors + request.callstack = []; //reset + bf = new framework.aop('/tests/issue518', {}); + + //Need to create different After interceptors + bf.declareBean("AfterInterceptorA", "tests.issue518.interceptors.aop.AfterInterceptor", true, {name = "afterA"}); + bf.declareBean("AfterInterceptorB", "tests.issue518.interceptors.aop.AfterInterceptor", true, {name = "afterAlterResultB"}); + bf.declareBean("AfterInterceptorC", "tests.issue518.interceptors.aop.AfterInterceptor", true, {name = "afterC"}); + + + bf.intercept("ReverseService", "AfterInterceptorA"); + bf.intercept("ReverseService", "AfterInterceptorB"); + bf.intercept("ReverseService", "AfterInterceptorC"); + + + rs = bf.getBean("ReverseService"); + result = rs.doReverse("Hello!"); + + AssertEquals(reverse("Hello!") & ",afterAlterResultB", result); + AssertEquals(4, arrayLen(request.callstack)); + AssertEquals("doReverse,afterA,afterAlterResultB,afterC", arrayToList(request.callstack)); + } + + + function TestMultipleAroundInterceptors() { + //Multiple Around Advisors + request.callstack = []; //reset + bf = new framework.aop('/tests/issue518', {}); + + + //Need to create different After interceptors + bf.declareBean("AroundInterceptorA", "tests.issue518.interceptors.aop.AroundInterceptor", true, {name = "aroundA"}); + bf.declareBean("AroundInterceptorB", "tests.issue518.interceptors.aop.AroundInterceptor", true, {name = "aroundB"}); + bf.declareBean("AroundInterceptorC", "tests.issue518.interceptors.aop.AroundInterceptor", true, {name = "aroundC"}); + + + bf.intercept("ReverseService", "AroundInterceptorA"); + bf.intercept("ReverseService", "AroundInterceptorB"); + bf.intercept("ReverseService", "AroundInterceptorC"); + rs = bf.getBean("ReverseService"); + + result = rs.doReverse("Hello!"); + + AssertEquals("aroundA,aroundB,aroundC," & reverse("Hello!") & ",aroundC,aroundB,aroundA", result); + AssertEquals(4, arrayLen(request.callstack)); + AssertEquals("aroundA,aroundB,aroundC,doReverse", arrayToList(request.callstack)); + } + + + function TestMethodMatches() { + bf = new framework.ioc('/tests/issue518', {}); + rs = bf.getBean("Reverse"); + + proxy = new framework.beanProxy(rs, [], {}); + makePublic( proxy, "methodMatches" ); + + AssertFalse(proxy.methodMatches("doForward", "doReverse")); + AssertTrue(proxy.methodMatches("doForward", "")); + AssertTrue(proxy.methodMatches("doForward", "*")); + AssertFalse(proxy.methodMatches("doForward", "doReverse,")); + AssertTrue(proxy.methodMatches("doForward", "doReverse,doForward")); + } + + + function TestNamedMethodInterceptions() { + //Named Method Interceptions + + request.callstack = []; //reset + bf = new framework.aop('/tests/issue518', {}); + //add an Interceptor + bf.intercept("ReverseService", "BeforeInterceptor", "doReverse"); + + rs = bf.getBean("ReverseService"); + + // This should be intercepted. + result = rs.doReverse("Hello!"); + + // This shoud not be intercepted. + result2 = rs.doForward("Hello!"); + + + // This should be intercepted. + result3 = rs.doReverse("Hello!"); + + AssertEquals(reverse("beforeHello!"), result); + AssertEquals("hello!", result2); + AssertEquals(reverse("beforeHello!"), result3); + AssertEquals(5, arrayLen(request.callstack)); + AssertEquals("before,doReverse,doForward,before,doReverse", arrayToList(request.callstack)); + } + + + function TestOnErrorInterceptors() { + request.callstack = []; //reset + bf = new framework.aop('/tests/issue518', {}); + rs = bf.getBean("ReverseService"); + result2 = rs.doForward("Hello!"); + + + AssertEquals("Hello!", result2); + AssertEquals(1, arrayLen(request.callstack)); + AssertEquals("doForward", arrayToList(request.callstack)); + + + request.callstack = []; //reset + bf = new framework.aop('/tests/issue518', {}); + //add an Interceptor + bf.intercept("ReverseService", "ErrorInterceptor", "throwError"); + + rs = bf.getBean("ReverseService"); + rs.throwError(); + + AssertEquals(2, arrayLen(request.callstack)); + AssertEquals("throwError,onError", arrayToList(request.callstack)); + } + + + function TestPrivateMethodInterceptors() { + request.callstack = []; //reset + bf = new framework.aop('/tests/issue518', {initMethod = "configure"}); + + //add an Interceptor + bf.intercept("advReverseService", "BeforeInterceptor", "doFront"); + + rs = bf.getBean("advReverseService"); + + result = rs.doWrap("Hello!"); + + AssertEquals("front-beforeHello!-rear", result); + AssertEquals(7, arrayLen(request.callstack)); + AssertEquals("init,setStackLog,configure,doWrap,before,doFront,doRear", arrayToList(request.callstack)); + } + + + function TestSingleInterceptorOnMultipleObjects() { + //Multiple Around Advisors + request.callstack = []; //reset + bf = new framework.aop('/tests/issue518', {}); + + + //Need to create different After interceptors + bf.declareBean("AroundInterceptorA", "tests.issue518.interceptors.aop.AroundInterceptor", true, {name = "aroundA"}); + + + bf.intercept("advReverse", "AroundInterceptorA", "doWrap"); + bf.intercept("ReverseService", "AroundInterceptorA", "doReverse"); + + rs = bf.getBean("ReverseService"); + ars = bf.getBean("advReverseService"); + + result = ars.doWrap(rs.doReverse("Hello!")); + + AssertEquals("aroundA,front-aroundA," & reverse("Hello!") & ",aroundA-rear,aroundA", result); + AssertEquals(8, arrayLen(request.callstack)); + AssertEquals("init,setStackLog,aroundA,doReverse,aroundA,doWrap,doFront,doRear", arrayToList(request.callstack)); + } +} diff --git a/tests/coreFunctions.cfc b/tests/coreFunctions.cfc new file mode 100644 index 00000000..8f352cab --- /dev/null +++ b/tests/coreFunctions.cfc @@ -0,0 +1,52 @@ +component extends=testbox.system.BaseSpec { + + function beforeAll() { + variables.fw = new framework.one(); + variables.fw.__config = __config; + } + + function afterAll() { + } + + function run( testResults, testBox ) { + + describe( "getDefaultSubsystem", function(){ + beforeEach(function(){ + structDelete( request, "subsystem" ); + structDelete( request, "section" ); + structAppend( variables.fw.__config(), { + usingSubsystems : false, + subsystemDelimiter : "@", + defaultSubsystem : "", + defaultSection : "section", + defaultItem : "item" + }); + }); + it( "should return empty string without subsystems", function(){ + expect( fw.getDefaultSubsystem() ).toBeEmpty(); + }); + it( "should return request subsystem, if present", function(){ + fw.__config().usingSubsystems = true; + request.subsystem = "requested"; + expect( fw.getDefaultSubsystem() ).toBe( "requested" ); + }); + it( "should return default subsystem, if request not present", function(){ + fw.__config().usingSubsystems = true; + fw.__config().defaultSubsystem = "subsystem"; + expect( fw.getDefaultSubsystem() ).toBe( "subsystem" ); + }); + it( "should throw an exception, if no default", function(){ + fw.__config().usingSubsystems = true; + expect(function(){ + return fw.getDefaultSubsystem(); + }).toThrow( type = "FW1.subsystemNotSpecified" ); + }); + }); + + } + + function __config() { + return variables.framework; + } + +} diff --git a/tests/issue518/Log.cfc b/tests/issue518/Log.cfc new file mode 100644 index 00000000..92f22c9d --- /dev/null +++ b/tests/issue518/Log.cfc @@ -0,0 +1,11 @@ +component{ + + function init(){ + return this; + } + + function logMessage(message, severity="information"){ + writelog(message, severity); + } + +} \ No newline at end of file diff --git a/tests/issue518/daos/advReverse.cfc b/tests/issue518/daos/advReverse.cfc new file mode 100644 index 00000000..cfe9c8b5 --- /dev/null +++ b/tests/issue518/daos/advReverse.cfc @@ -0,0 +1,46 @@ +component displayname="advReverseService" extends="tests.issue518.services.Reverse" accessors="true" output="false" { + + + // PUBLIC METHODS + public function configure() { + getStackLog().log("wrong-configure"); + return this; + } + + + public function doWrap(string input) { + getStackLog().log("wrong-doWrap"); + return doRear(doFront(arguments.input)); + } + + + public function init() { + // stackLog does not exist at this point. + arrayAppend(request.callStack, "wrong-init"); + + return super.init(); + } + + + public function setStackLog(any stackLog) { + // stackLog does not exist at this point. + arrayAppend(request.callStack, "wrong-setStackLog"); + + variables.stackLog = arguments.stackLog; + } + + + + + // PRIVATE METHODS + private function doFront(string input) { + getStackLog().log("wrong-doFront"); + return "front-" & arguments.input; + } + + + private function doRear(string input) { + getStackLog().log("wrong-doRear"); + return arguments.input & "-rear"; + } +} diff --git a/tests/issue518/interceptors/BasicInterceptor.cfc.test b/tests/issue518/interceptors/BasicInterceptor.cfc.test new file mode 100644 index 00000000..9573ff5c --- /dev/null +++ b/tests/issue518/interceptors/BasicInterceptor.cfc.test @@ -0,0 +1,39 @@ +/** +* +* @author @markdrew +* @description This is a demo interceptor +* +*/ +component output="false" displayname="BasicInterceptor" { + + this.name = "A"; + + public function init(name="A"){ + this.name = name; + return this; + } + + + //basically it's onMissingMethod! + function before(method, args, target){ + param name="request.callstack" default="#[]#"; + + arguments.args.1 = "before" & arguments.args.1 + } + + function after(){ + param name="request.callstack" default="#[]#"; + arguments.result = arguments.result & "after"; + + } + + function onMethod(){ + param name="request.callstack" default="#[]#"; + dump(var=arguments, label="onMethod"); + } + + function onError(){ + param name="request.callstack" default="#[]#"; + dump(var=arguments, label="onError"); + } +} \ No newline at end of file diff --git a/tests/issue518/interceptors/aop/AfterInterceptor.cfc b/tests/issue518/interceptors/aop/AfterInterceptor.cfc new file mode 100644 index 00000000..1afc9395 --- /dev/null +++ b/tests/issue518/interceptors/aop/AfterInterceptor.cfc @@ -0,0 +1,18 @@ +component displayname="AfterInterceptor" extends="interceptor" accessors="true" output="false" { + + + function init(name="after") { + this.name=name; + } + + + function after(target, method, args, result) { + getStackLog().log(this.name); + + // Demonstrate that we can alter the result. + if (findNoCase("alter", this.name) && structKeyExists(arguments, "result") && !isNull(arguments.result)) + { + return arguments.result & "," & this.name; + } + } +} \ No newline at end of file diff --git a/tests/issue518/interceptors/aop/AroundInterceptor.cfc b/tests/issue518/interceptors/aop/AroundInterceptor.cfc new file mode 100644 index 00000000..e4ad95fa --- /dev/null +++ b/tests/issue518/interceptors/aop/AroundInterceptor.cfc @@ -0,0 +1,27 @@ +component displayname="AroundInterceptor" extends="interceptor" accessors="true" output="false" { + + + function init(name="around") { + this.name=name; + } + + + function around(target, method, args) { + getStackLog().log(this.name); + + local.result = proceed(arguments.target, arguments.method, arguments.args); + + // This runs on 'set...' methods as well for properties. Limit to simple result calls. + if (structKeyExists(local, "result") && !isNull(local.result) && isSimpleValue(local.result)) + { + return this.name & "," & local.result & "," & this.name; + } + else + { + writeDump(var = isNull(arguments.target)); + writeDump(var = isNull(arguments.target.getStackLog())); + writeDump(var = arguments.method); + writeDump(var = structKeyList(arguments.target), abort = true); + } + } +} diff --git a/tests/issue518/interceptors/aop/BeforeInterceptor.cfc b/tests/issue518/interceptors/aop/BeforeInterceptor.cfc new file mode 100644 index 00000000..f3099685 --- /dev/null +++ b/tests/issue518/interceptors/aop/BeforeInterceptor.cfc @@ -0,0 +1,20 @@ +component displayname="BeforeInterceptor" extends="interceptor" accessors="true" output="false" { + + + function init(name="before") { + this.name=name; + } + + + function before(target, method, args) { + getStackLog().log(this.name); + + translateArgs(target, method, args, true); + + // Demonstrate that we can alter the arguments before the method call. + if (structKeyExists(arguments.args, "input")) + { + arguments.args.input = "before" & arguments.args.input; + } + } +} \ No newline at end of file diff --git a/tests/issue518/interceptors/aop/ErrorInterceptor.cfc b/tests/issue518/interceptors/aop/ErrorInterceptor.cfc new file mode 100644 index 00000000..f51908db --- /dev/null +++ b/tests/issue518/interceptors/aop/ErrorInterceptor.cfc @@ -0,0 +1,12 @@ +component output="false" { + + this.name = "onError"; + function init(name="onError"){ + this.name=name; + } + + function onError(method,args,target, error){ + ArrayAppend(request.callstack, this.name); + + } +} \ No newline at end of file diff --git a/tests/issue518/interceptors/aop/interceptor.cfc b/tests/issue518/interceptors/aop/interceptor.cfc new file mode 100644 index 00000000..a1f987a0 --- /dev/null +++ b/tests/issue518/interceptors/aop/interceptor.cfc @@ -0,0 +1,8 @@ +component displayname="interceptor" accessors="true" output="false" { + + + property name="stackLog"; + + + this.name = ""; +} diff --git a/tests/issue518/interceptors/example/Logger.cfc b/tests/issue518/interceptors/example/Logger.cfc new file mode 100644 index 00000000..098be17e --- /dev/null +++ b/tests/issue518/interceptors/example/Logger.cfc @@ -0,0 +1,14 @@ +component { + + function init(LogService){ + this.logService = logService; + return this; + } + function before(methodname, args, target){ + this.logService.logMessage("Before:" & arguments.args.input); + + } + function after(result, methodname, args, target){ + this.logService.logMessage("After:" & arguments.result); + } +} \ No newline at end of file diff --git a/tests/issue518/service.cfc b/tests/issue518/service.cfc new file mode 100644 index 00000000..97465f72 --- /dev/null +++ b/tests/issue518/service.cfc @@ -0,0 +1,15 @@ +component displayname="service" accessors="true" output="false" { + + + property name="stackLog"; + + + public function getServiceName() { + return listLast(getMetadata(this)); + } + + + public function init() { + return this; + } +} diff --git a/tests/issue518/services/Reverse.cfc b/tests/issue518/services/Reverse.cfc new file mode 100644 index 00000000..06c2b3b5 --- /dev/null +++ b/tests/issue518/services/Reverse.cfc @@ -0,0 +1,22 @@ +component displayname="reverseService" extends="tests.issue518.string" accessors="true" output="false" { + + + public function doForward(string input) { + //I double reverse a string... i.e. do nothing! + getStackLog().log("doForward"); + return reverse(reverse(arguments.input)); + } + + + public function doReverse(string input) { + getStackLog().log("doReverse"); + return reverse(arguments.input); + } + + + public function throwError() { + //This is just to throw an error + getStackLog().log("throwError"); + throw "I AM AN EVIL ERROR YOU WANT TO TRAP!"; + } +} diff --git a/tests/issue518/services/advReverse.cfc b/tests/issue518/services/advReverse.cfc new file mode 100644 index 00000000..21bd3d4d --- /dev/null +++ b/tests/issue518/services/advReverse.cfc @@ -0,0 +1,46 @@ +component displayname="advReverseService" extends="Reverse" accessors="true" output="false" { + + + // PUBLIC METHODS + public function configure() { + getStackLog().log("configure"); + return this; + } + + + public function doWrap(string input) { + getStackLog().log("doWrap"); + return doRear(doFront(arguments.input)); + } + + + public function init() { + // stackLog does not exist at this point. + arrayAppend(request.callStack, "init"); + + return super.init(); + } + + + public function setStackLog(any stackLog) { + // stackLog does not exist at this point. + arrayAppend(request.callStack, "setStackLog"); + + variables.stackLog = arguments.stackLog; + } + + + + + // PRIVATE METHODS + private function doFront(string input) { + getStackLog().log("doFront"); + return "front-" & arguments.input; + } + + + private function doRear(string input) { + getStackLog().log("doRear"); + return arguments.input & "-rear"; + } +} diff --git a/tests/issue518/services/array.cfc b/tests/issue518/services/array.cfc new file mode 100644 index 00000000..2e6a4880 --- /dev/null +++ b/tests/issue518/services/array.cfc @@ -0,0 +1,9 @@ +component displayname="arrayService" extends="tests.issue518.service" accessors="true" output="false" { + + + public array function doListToArray(string list) { + //I double reverse a string... i.e. do nothing! + getStackLog().log("doListToArray"); + return listToArray(arguments.list); + } +} diff --git a/tests/issue518/stackLog.cfc b/tests/issue518/stackLog.cfc new file mode 100644 index 00000000..a9232aef --- /dev/null +++ b/tests/issue518/stackLog.cfc @@ -0,0 +1,17 @@ +component displayname="stackLog" extends="service" output="false" { + + + public function init() { + if (!structKeyExists(request, "callStack")) + { + request["callStack"] = []; + } + + return super.init(); + } + + + public function log(string message) { + arrayAppend(request.callStack, message); + } +} diff --git a/tests/issue518/string.cfc b/tests/issue518/string.cfc new file mode 100644 index 00000000..e7bdf787 --- /dev/null +++ b/tests/issue518/string.cfc @@ -0,0 +1,10 @@ +component displayname="string" extends="service" accessors="true" output="false" { + + + property name="stackLog"; + + + public function init() { + return this; + } +} diff --git a/tests/model/beans/person.cfc b/tests/model/beans/person.cfc new file mode 100644 index 00000000..7e83e014 --- /dev/null +++ b/tests/model/beans/person.cfc @@ -0,0 +1,9 @@ +component accessors="true" { + + property name="firstname" default=""; + property name="lastname" default=""; + + function init(){ + return this; + } +} \ No newline at end of file diff --git a/tests/specs/injectPropertiesTest.cfc b/tests/specs/injectPropertiesTest.cfc new file mode 100644 index 00000000..c91d5b06 --- /dev/null +++ b/tests/specs/injectPropertiesTest.cfc @@ -0,0 +1,31 @@ +component extends="testbox.system.BaseSpec" { + // executes before all suites + function beforeAll(){ + ioc = new framework.ioc( "" ); + ioc2 = new framework.ioc( "/tests/model" ); + } + + // executes after all suites + function afterAll(){} + + // All suites go in here + function run( testResults, testBox ){ + describe("A bean injected with properties", function(){ + it("errors on undefined properties in the bean", function(){ + var bean = new tests.model.beans.person(); + + expect( function(){ + ioc.injectProperties( bean=bean, properties= { firstName="steven", thirdName="fail"} ); + }).toThrow(); + }); + + it("does not error on undefined properties in the bean when ignoreMissing is specified", function(){ + var bean = new tests.model.beans.person(); + + ioc.injectProperties( bean=bean, properties= { firstName="steven", thirdName="fail"}, ignoreMissing=true); + expect( bean.getFirstName() ).toBe("steven"); + }); + + }); + } +}