diff --git a/.cfconfig.json b/.cfconfig.json index ffd12ff52..581c97583 100644 --- a/.cfconfig.json +++ b/.cfconfig.json @@ -1,7 +1,15 @@ { - "systemErr":"System", - "systemOut":"System", "adminPassword" : "coldbox", + "ajaxDebugWindowEnabled": false, + "debuggingEnabled": false, + "debuggingReportExecutionTimes": false, + "inspectTemplate":"always", + "maxCFThreads":100, + "requestTimeout":"0,0,0,90", + "robustExceptionEnabled":true, + "systemErr":"System", + "systemOut":"System", + "whitespaceManagement":"white-space-pref", "caches": { "default": { "storage": "false", @@ -16,19 +24,31 @@ "cacheDefaultObject": "default", "datasources": { "coolblog": { - "clientHostname": false, - "host": "localhost", - "dbdriver": "MySQL", + "allowAlter":true, + "allowCreate":true, + "allowDelete":true, + "allowDrop":true, + "allowGrant":true, + "allowInsert":true, + "allowRevoke":true, + "allowSelect":true, + "allowUpdate":true, + "blob":"false", + "clob":"false", + "connectionTimeout":"1", + "class":"${DB_CLASS}", + "dbdriver": "MySQL", + "dsn":"jdbc:mysql://{host}:{port}/{database}", + "custom":"useUnicode=true&characterEncoding=UTF8&serverTimezone=America%2FChicago&useLegacyDatetimeCode=true&autoReconnect=true&useSSL=false", + "host":"${DB_HOST:127.0.0.1}", + "username": "${DB_USER}", "password": "${DB_PASSWORD}", - "dsn": "${DB_CONNECTIONSTRING}", - "custom": "", - "username": "${DB_USER:root}", "database": "coolblog", - "port": "3306" + "port": "${DB_PORT:3306}", + "storage":"false", + "bundleName": "${DB_BUNDLENAME}", + "bundleVersion": "${DB_BUNDLEVERSION}", + "validate":"false" } - }, - "debuggingEnabled": false, - "ajaxDebugWindowEnabled": false, - "debuggingReportExecutionTimes": false, - "maxCFThreads":100 + } } \ No newline at end of file diff --git a/.cfformat.json b/.cfformat.json index 68fb86b25..c05ed4c59 100644 --- a/.cfformat.json +++ b/.cfformat.json @@ -41,7 +41,7 @@ "keywords.spacing_to_block": "spaced", "keywords.spacing_to_group": true, "keywords.empty_group_spacing": false, - "max_columns": 100, + "max_columns": 115, "metadata.multiline.element_count": 3, "metadata.multiline.min_length": 50, "method_call.chain.multiline" : 3, diff --git a/.env.template b/.env.template index a02ee07eb..69e19d5a4 100644 --- a/.env.template +++ b/.env.template @@ -1,4 +1,7 @@ -DB_CLASS=com.mysql.jdbc.Driver -DB_CONNECTIONSTRING=jdbc:mysql://localhost:3306/coolblog?useUnicode=true&characterEncoding=UTF-8&useLegacyDatetimeCode=true&autoConfigureForColdFusion=false&serverTimezone=UTC +DB_HOST=127.0.0.1 +DB_PORT=3306 DB_USER=root -DB_PASSWORD= \ No newline at end of file +DB_PASSWORD=mysql +DB_CLASS=com.mysql.jdbc.Driver +DB_BUNDLEVERSION=8.0.19 +DB_BUNDLENAME=com.mysql.cj \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 93333c8d8..eedb1920d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: java os: "linux" -group: deprecated-2017Q2 +dist: focal notifications: slack: @@ -9,7 +9,7 @@ notifications: env: global: # TARGET RELEASE VERSION: BUMP AS NEEDED - - COLDBOX_VERSION=6.1.0 + - COLDBOX_VERSION=6.2.0 - COLDBOX_PRERELEASE=false matrix: - ENGINE=lucee@5 # Build Entire Frameworks @@ -21,15 +21,8 @@ branches: - development - master -dist: xenial services: - mysql -#addons: -# apt: -# packages: -# - mysql-server-5.6 -# - mysql-client-core-5.6 -# - mysql-client-5.6 before_install: # CommandBox Keys @@ -52,27 +45,36 @@ before_script: - mysql -u root -e 'create database coolblog;' # import database - mysql -u root < tests/resources/coolblog.sql - # add our custom engine to our properties - - printf "\ncfengine=$ENGINE" >> build/build.properties - - echo "************************ Runtime Properties ********************" - - cat build/build.properties - - echo "************************ END Runtime Properties ********************" - # Starting server twice due to stupid LUCEE 5 bug. - - box server start name=$ENGINE debug=true + # Seed our .env + - echo "Seeding .env file" + - touch .env + - printf "DB_USER=travis\n" >> .env + - printf "DB_PASSWORD=\n" >> .env + - printf "DB_BUNDLEVERSION=8.0.19\n" >> .env + - printf "DB_BUNDLENAME=com.mysql.cj\n" >> .env + # This is the key for MySQL 8 to work + - printf "DB_CLASS=com.mysql.cj.jdbc.Driver\n" >> .env script: + # install dependencies + - box install + - cd apidocs && box install + - cd $TRAVIS_BUILD_DIR + # Startup the server to test + - box server start serverConfigFile="server-${ENGINE}.json" --debug # Execute build via ANT - ant -DisPreRelease=${COLDBOX_PRERELEASE} -Dcoldbox.version=$COLDBOX_VERSION -DisTravis=true -Dbuild.branch=$TRAVIS_BRANCH -Dbuild.number=$TRAVIS_BUILD_NUMBER -f build/build.xml $ANT_TARGET #- ls -l $TRAVIS_BUILD_DIR after_failure: - cd $TRAVIS_BUILD_DIR + - box cfconfig show # Get response from test server to see what went wrong - curl http://localhost:8599/tests/runner.cfm?reporter=text - - curl http://localhost:8599/tests/tools/IDEDictionaries/builderDictionary.cfm?text=true + #- curl http://localhost:8599/tests/tools/IDEDictionaries/builderDictionary.cfm?text=true # Spit out our Commandbox log in case we need to debug - - box server log name=$ENGINE - - cat `box system-log` + - box server log serverConfigFile="server-${ENGINE}.json" + #- cat `box system-log` - ls -lr $TRAVIS_BUILD_DIR deploy: @@ -148,4 +150,4 @@ after_deploy: # publish WireBox - cd $TRAVIS_BUILD_DIR/artifacts/wirebox/$COLDBOX_VERSION && box forgebox publish # publish LogBox - - cd $TRAVIS_BUILD_DIR/artifacts/logbox/$COLDBOX_VERSION && box forgebox publish + - cd $TRAVIS_BUILD_DIR/artifacts/logbox/$COLDBOX_VERSION && box forgebox publish \ No newline at end of file diff --git a/apidocs/box.json b/apidocs/box.json index 6417fbc43..df6906753 100644 --- a/apidocs/box.json +++ b/apidocs/box.json @@ -5,10 +5,8 @@ "dependencies":{ "docbox":"^2.0.7" }, - "devDependencies":{ - - }, + "devDependencies":{}, "installPaths":{ - "docbox":"docbox" + "docbox":"docbox/" } } \ No newline at end of file diff --git a/box.json b/box.json index 9b6f9e698..ef06f1c07 100644 --- a/box.json +++ b/box.json @@ -39,7 +39,11 @@ ] }, "devDependencies":{ - "testbox":"^4.1.0" + "testbox":"^4.1.0", + "commandbox-dotenv":"*", + "commandbox-cfconfig":"*", + "commandbox-cfformat":"*", + "commandbox-docbox":"*" }, "installPaths":{ "testbox":"testbox/" diff --git a/build/Build.cfc b/build/Build.cfc new file mode 100644 index 000000000..cf74dff98 --- /dev/null +++ b/build/Build.cfc @@ -0,0 +1,272 @@ +/** + * Build process for ColdBox Modules + * Adapt to your needs. + */ +component{ + + /** + * Constructor + */ + function init(){ + // Setup Pathing + variables.cwd = getCWD().reReplace( "\.$", "" ); + variables.artifactsDir = cwd & "/.artifacts"; + variables.buildDir = cwd & "/.tmp"; + variables.apiDocsURL = "http://localhost:60299/apidocs/"; + variables.testRunner = "http://localhost:60299/tests/runner.cfm"; + + // Source Excludes Not Added to final binary + variables.excludes = [ + ".gitignore", + ".travis.yml", + ".artifacts", + ".tmp", + "build", + "test-harness", + ".DS_Store", + ".git" + ]; + + // Cleanup + Init Build Directories + [ variables.buildDir, variables.artifactsDir ].each( function( item ){ + if( directoryExists( item ) ){ + directoryDelete( item, true ); + } + // Create directories + directoryCreate( item, true, true ); + } ); + + // Create Mappings + fileSystemUtil.createMapping( "coldbox", variables.cwd & "test-harness/coldbox" ); + + return this; + } + + /** + * Run the build process: test, build source, docs, checksums + * + * @projectName The project name used for resources and slugs + * @version The version you are building + * @buldID The build identifier + * @branch The branch you are building + */ + function run( + required projectName, + version="1.0.0", + buildID=createUUID(), + branch="development" + ){ + // Create project mapping + fileSystemUtil.createMapping( arguments.projectName, variables.cwd ); + + // Run the tests + runTests(); + + // Build the source + buildSource( argumentCollection=arguments ); + + // Build Docs + arguments.outputDir = variables.buildDir & "/apidocs"; + docs( argumentCollection=arguments ); + + // checksums + buildChecksums(); + + // Build latest changelog + latestChangelog(); + + // Finalize Message + print.line() + .boldMagentaLine( "Build Process is done! Enjoy your build!" ) + .toConsole(); + } + + /** + * Run the test suites + */ + function runTests(){ + // Tests First, if they fail then exit + print.blueLine( "Testing the package, please wait..." ).toConsole(); + + command( 'testbox run' ) + .params( + runner = variables.testRunner, + verbose = true, + outputFile = "build/results.json" + ) + .run(); + + // Check Exit Code? + if( shell.getExitCode() ){ + return error( "Cannot continue building, tests failed!" ); + } + } + + /** + * Build the source + * + * @projectName The project name used for resources and slugs + * @version The version you are building + * @buldID The build identifier + * @branch The branch you are building + */ + function buildSource( + required projectName, + version="1.0.0", + buildID=createUUID(), + branch="development" + ){ + // Build Notice ID + print.line() + .boldMagentaLine( "Building #arguments.projectName# v#arguments.version#+#arguments.buildID# from #cwd# using the #arguments.branch# branch." ) + .toConsole(); + + // Prepare exports directory + variables.exportsDir = variables.artifactsDir & "/#projectName#/#arguments.version#"; + directoryCreate( variables.exportsDir, true, true ); + + // Project Build Dir + variables.projectBuildDir = variables.buildDir & "/#projectName#"; + directoryCreate( variables.projectBuildDir, true, true ); + + // Copy source + print.blueLine( "Copying source to build folder..." ).toConsole(); + copy( variables.cwd, variables.projectBuildDir ); + + // Create build ID + fileWrite( "#variables.projectBuildDir#/#projectName#-#version#+#buildID#", "Built with love on #dateTimeFormat( now(), "full")#" ); + + // Updating Placeholders + print.greenLine( "Updating version identifier to #arguments.version#" ).toConsole(); + command( 'tokenReplace' ) + .params( + path = "/#variables.projectBuildDir#/**", + token = "@build.version@", + replacement = arguments.version + ) + .run(); + + print.greenLine( "Updating build identifier to #arguments.buildID#" ).toConsole(); + command( 'tokenReplace' ) + .params( + path = "/#variables.projectBuildDir#/**", + token = ( arguments.branch == "master" ? "@build.number@" : "+@build.number@" ), + replacement = ( arguments.branch == "master" ? arguments.buildID : "-snapshot" ) + ) + .run(); + + // zip up source + var destination = "#variables.exportsDir#/#projectName#-#version#.zip"; + print.greenLine( "Zipping code to #destination#" ).toConsole(); + cfzip( + action="zip", + file="#destination#", + source="#variables.projectBuildDir#", + overwrite=true, + recurse=true + ); + + // Copy box.json for convenience + fileCopy( "#variables.projectBuildDir#/box.json", variables.exportsDir ); + } + + /** + * Produce the API Docs + */ + function docs( required projectName, version="1.0.0", outputDir=".tmp/apidocs" ){ + // Generate Docs + print.greenLine( "Generating API Docs, please wait..." ).toConsole(); + directoryCreate( arguments.outputDir, true, true ); + + command( 'docbox generate' ) + .params( + "source" = "models", + "mapping" = "models", + "strategy-projectTitle" = "#arguments.projectName# v#arguments.version#", + "strategy-outputDir" = arguments.outputDir + ) + .run(); + + print.greenLine( "API Docs produced at #arguments.outputDir#" ).toConsole(); + + var destination = "#variables.exportsDir#/#projectName#-docs-#version#.zip"; + print.greenLine( "Zipping apidocs to #destination#" ).toConsole(); + cfzip( + action="zip", + file="#destination#", + source="#arguments.outputDir#", + overwrite=true, + recurse=true + ); + } + + /** + * Build the latest changelog file: changelog-latest.md + */ + function latestChangelog(){ + print.blueLine( "Building latest changelog..." ).toConsole(); + + if( !fileExists( variables.cwd & "changelog.md" ) ){ + return error( "Cannot continue building, changelog.md file doesn't exist!" ); + } + + fileWrite( + variables.cwd & "changelog-latest.md", + fileRead( variables.cwd & 'changelog.md' ).split( '----' )[2].trim() & chr( 13 ) & chr( 10 ) + ); + + print + .greenLine( "Latest changelog file created at `changelog-latest.md`" ) + .line() + .line( fileRead( variables.cwd & "changelog-latest.md" ) ); + } + + /********************************************* PRIVATE HELPERS *********************************************/ + + /** + * Build Checksums + */ + private function buildChecksums(){ + print.greenLine( "Building checksums" ).toConsole(); + command( 'checksum' ) + .params( path = '#variables.exportsDir#/*.zip', algorithm = 'SHA-512', extension="sha512", write=true ) + .run(); + command( 'checksum' ) + .params( path = '#variables.exportsDir#/*.zip', algorithm = 'md5', extension="md5", write=true ) + .run(); + } + + /** + * DirectoryCopy is broken in lucee + */ + private function copy( src, target, recurse=true ){ + // process paths with excludes + directoryList( src, false, "path", function( path ){ + var isExcluded = false; + variables.excludes.each( function( item ){ + if( path.replaceNoCase( variables.cwd, "", "all" ).findNoCase( item ) ){ + isExcluded = true; + } + } ); + return !isExcluded; + }).each( function( item ){ + // Copy to target + if( fileExists( item ) ){ + print.blueLine( "Copying #item#" ).toConsole(); + fileCopy( item, target ); + } else { + print.greenLine( "Copying directory #item#" ).toConsole(); + directoryCopy( item, target & "/" & item.replace( src, "" ), true ); + } + } ); + } + + /** + * Gets the last Exit code to be used + **/ + private function getExitCode() { + return (createObject( 'java', 'java.lang.System' ).getProperty( 'cfml.cli.exitCode' ) ?: 0); + + } + +} \ No newline at end of file diff --git a/build/build.xml b/build/build.xml index 68310d88c..afaa3f3e2 100644 --- a/build/build.xml +++ b/build/build.xml @@ -47,9 +47,6 @@ - - - @@ -85,17 +82,6 @@ - - - - - - - - - - - @@ -612,15 +598,4 @@ encoding="UTF-8"/> - - - - - - - - - - - \ No newline at end of file diff --git a/server-adobe@2018.json b/server-adobe@2018.json index e26ce2b5e..92aaade9f 100644 --- a/server-adobe@2018.json +++ b/server-adobe@2018.json @@ -13,8 +13,8 @@ }, "rewrites":{ "enable":true - }, - "aliases":{ + }, + "aliases":{ "/coldbox":"./" } }, diff --git a/server-adobe@2021.json b/server-adobe@2021.json new file mode 100644 index 000000000..b6ed15737 --- /dev/null +++ b/server-adobe@2021.json @@ -0,0 +1,24 @@ +{ + "app":{ + "cfengine":"adobe@2021", + "serverHomeDirectory":".engine/adobe2021" + }, + "name":"coldbox-adobe@2021", + "force":true, + "openBrowser":false, + "web":{ + "directoryBrowsing":true, + "http":{ + "port":"8599" + }, + "rewrites":{ + "enable":true + }, + "aliases":{ + "/coldbox":"./" + } + }, + "JVM":{ + "heapSize":"1024" + } +} \ No newline at end of file diff --git a/system/Bootstrap.cfc b/system/Bootstrap.cfc index ebf70c279..11c9a8279 100644 --- a/system/Bootstrap.cfc +++ b/system/Bootstrap.cfc @@ -25,13 +25,13 @@ component serializable="false" accessors="true"{ property name="COLDBOX_FAIL_FAST"; // param the properties with defaults - param name="COLDBOX_CONFIG_FILE" default=""; - param name="COLDBOX_APP_ROOT_PATH" default="#getDirectoryFromPath( getbaseTemplatePath() )#"; - param name="COLDBOX_APP_KEY" default="cbController"; - param name="COLDBOX_APP_MAPPING" default=""; - param name="appHash" default="#hash( getBaseTemplatePath() )#"; - param name="lockTimeout" default="30" type="numeric"; - param name="COLDBOX_FAIL_FAST" default="true"; + param name="COLDBOX_CONFIG_FILE" default=""; + param name="COLDBOX_APP_ROOT_PATH" default="#getDirectoryFromPath( getbaseTemplatePath() )#"; + param name="COLDBOX_APP_KEY" default="cbController"; + param name="COLDBOX_APP_MAPPING" default=""; + param name="appHash" default="#hash( getBaseTemplatePath() )#"; + param name="lockTimeout" default="30" type="numeric"; + param name="COLDBOX_FAIL_FAST" default="true"; /** * Constructor, called by your Application CFC @@ -46,7 +46,7 @@ component serializable="false" accessors="true"{ required string COLDBOX_APP_ROOT_PATH, string COLDBOX_APP_KEY, string COLDBOX_APP_MAPPING="", - any COLDBOX_FAIL_FAST=true + any COLDBOX_FAIL_FAST =true ){ // Set vars for two main locations setCOLDBOX_CONFIG_FILE( arguments.COLDBOX_CONFIG_FILE ); @@ -84,7 +84,7 @@ component serializable="false" accessors="true"{ ); throw( message = "Cannot find the '/'coldbox' mapping", - detail = "It seems that you do not have a '/coldbox' mapping in your application and we cannot continue to process the request. + detail = "It seems that you do not have a '/coldbox' mapping in your application and we cannot continue to process the request. The good news is that you can easily resolve this by either creating a mapping in your Admnistrator or in this application's Application.cfc that points to this directory: '#coldboxDirectory#'. You can also copy the code snippet below to add to your Application.cfc's pseudo constructor: this.mappings[ '/coldbox' ] = '#coldboxDirectory#'", @@ -123,9 +123,9 @@ component serializable="false" accessors="true"{ * Request Reload procedures */ function reloadChecks(){ - var appKey = locateAppKey(); - var cbController = ""; - var needReinit = isfwReinit(); + var appKey = locateAppKey(); + var cbController = ""; + var needReinit = isfwReinit(); // Initialize the Controller If Needed, double locked if( NOT structkeyExists( application, appkey ) OR NOT application[ appKey ].getColdboxInitiated() OR needReinit ){ @@ -194,8 +194,8 @@ component serializable="false" accessors="true"{ var cbController = application[ locateAppKey() ]; } // Local references - var interceptorService = cbController.getInterceptorService(); - var cacheBox = cbController.getCacheBox(); + var interceptorService = cbController.getInterceptorService(); + var cacheBox = cbController.getCacheBox(); try{ // set request time, for info purposes @@ -209,14 +209,14 @@ component serializable="false" accessors="true"{ interceptorService.announce( "preProcess" ); if( len( cbController.getSetting( "RequestStartHandler" ) ) ){ cbController.runEvent( - event : cbController.getSetting( "RequestStartHandler" ), - prePostExempt : true + event : cbController.getSetting( "RequestStartHandler" ), + prePostExempt : true ); } //****** EVENT CACHING CONTENT DELIVERY *******/ - var refResults = {}; - var eCacheEntry = event.getEventCacheableEntry(); + var refResults = {}; + var eCacheEntry= event.getEventCacheableEntry(); // Verify if event caching item is in selected cache if( eCacheEntry.keyExists( "cachekey" ) ){ @@ -328,14 +328,14 @@ component serializable="false" accessors="true"{ // prepare storage entry var cacheEntry = { - renderedContent = renderedContent, - renderData = false, - contentType = defaultContentType, - encoding = "", - statusCode = "", - statusText = "", - isBinary = false, - responseHeaders = event.getResponseHeaders() + renderedContent= renderedContent, + renderData = false, + contentType = defaultContentType, + encoding = "", + statusCode = "", + statusText = "", + isBinary = false, + responseHeaders= event.getResponseHeaders() }; // is this a render data entry? If So, append data @@ -400,7 +400,6 @@ component serializable="false" accessors="true"{ } - /** * Verify if a reinit is sent */ @@ -409,7 +408,7 @@ component serializable="false" accessors="true"{ // CF Parm Structures just in case param name="FORM" default="#structNew()#"; - param name="URL" default="#structNew()#"; + param name="URL" default="#structNew()#"; // Check if app exists already in scope if( not structKeyExists( application, appKey ) ){ @@ -545,7 +544,7 @@ component serializable="false" accessors="true"{ // Execute interceptors var iData = { - sessionReference = arguments.sessionScope, + sessionReference = arguments.sessionScope, applicationReference = arguments.appScope }; cbController.getInterceptorService().announce( "sessionEnd", iData ); @@ -596,11 +595,11 @@ component serializable="false" accessors="true"{ */ private string function processException( required controller, required exception ){ // prepare exception facade object + app logger - var oException = new coldbox.system.web.context.ExceptionBean( arguments.exception ); - var appLogger = arguments.controller.getLogBox().getLogger( this ); - var event = arguments.controller.getRequestService().getContext(); - var rc = event.getCollection(); - var prc = event.getPrivateCollection(); + var oException= new coldbox.system.web.context.ExceptionBean( arguments.exception ); + var appLogger = arguments.controller.getLogBox().getLogger( this ); + var event = arguments.controller.getRequestService().getContext(); + var rc = event.getCollection(); + var prc = event.getPrivateCollection(); // Announce interception arguments.controller.getInterceptorService() @@ -637,8 +636,8 @@ component serializable="false" accessors="true"{ if( len( arguments.controller.getSetting( "AppMapping" ) ) ){ appLocation = appLocation & arguments.controller.getSetting( "AppMapping" ) & "/"; } - var bugReportRelativePath = appLocation & reReplace( customErrorTemplate, "^/", "" ); - var bugReportAbsolutePath = customErrorTemplate; + var bugReportRelativePath = appLocation & reReplace( customErrorTemplate, "^/", "" ); + var bugReportAbsolutePath = customErrorTemplate; // Show Bug Report savecontent variable="local.exceptionReport"{ @@ -660,33 +659,7 @@ component serializable="false" accessors="true"{ return local.exceptionReport; } - /** - * Process Stack trace for errors - */ - private function processStackTrace( str ){ - // Not using encodeForHTML() as it is too destructive and ruins whitespace chars and other stuff - arguments.str = HTMLEditFormat( arguments.str ); - - var aMatches = REMatchNoCase( "\(([^\)]+)\)", arguments.str ); - for( var aString in aMatches ){ - arguments.str = replacenocase( arguments.str, aString, "#aString#", "all" ); - } - var aMatches = REMatchNoCase( "\[([^\]]+)\]", arguments.str ); - for( var aString in aMatches ){ - arguments.str = replacenocase( arguments.str, aString, "#aString#", "all" ); - } - var aMatches = REMatchNoCase( "\$([^(\(|\:)]+)(\:|\()", arguments.str ); - for( var aString in aMatches ){ - arguments.str = replacenocase( arguments.str, aString, "#aString#", "all" ); - } - arguments.str = replace( arguments.str, chr( 13 ) & chr( 10 ), chr( 13 ) , 'all' ); - arguments.str = replace( arguments.str, chr( 10 ), chr( 13 ) , 'all' ); - arguments.str = replace( arguments.str, chr( 13 ), '
' , 'all' ); - arguments.str = replaceNoCase( arguments.str, chr(9), repeatString( " ", 4 ), "all" ); - return arguments.str; - } - - /** + /** * Process render data setup * @controller The ColdBox controller * @statusCode The status code to send diff --git a/system/Interceptor.cfc b/system/Interceptor.cfc index 63b699fd5..1b7064792 100755 --- a/system/Interceptor.cfc +++ b/system/Interceptor.cfc @@ -65,10 +65,22 @@ component extends="coldbox.system.FrameworkSupertype" serializable="false" acces * @property The property to retrieve * @defaultValue The default value to return if property does not exist * + * @throws InvalidPropertyException * @return The property value requested or the default value if not found */ any function getProperty( required property, defaultValue ){ - return ( structKeyExists( variables.properties, arguments.property ) ? variables.properties[ arguments.property ] : arguments.defaultValue ); + if( structKeyExists( variables.properties, arguments.property ) ){ + return variables.properties[ arguments.property ]; + } + + if( !isNull( arguments.defaultValue ) ){ + return arguments.defaultValue; + } + + throw( + message = "The requested property #arguments.property# does not exist.", + type = "InvalidPropertyException" + ); } /** diff --git a/system/RestHandler.cfc b/system/RestHandler.cfc index b443b6258..ddbadc58d 100644 --- a/system/RestHandler.cfc +++ b/system/RestHandler.cfc @@ -87,7 +87,7 @@ component extends="EventHandler" { "Error calling #arguments.event.getCurrentEvent()#: #e.message# #e.detail#", { "_stacktrace" : e.stacktrace, - "httpData" : getHTTPRequestData() + "httpData" : getHTTPRequestData( false ) } ); @@ -220,7 +220,7 @@ component extends="EventHandler" { "Error in base handler (#arguments.faultAction#): #arguments.exception.message# #arguments.exception.detail#", { "_stacktrace" : arguments.exception.stacktrace, - "httpData" : getHTTPRequestData() + "httpData" : getHTTPRequestData( false ) } ); @@ -366,7 +366,7 @@ component extends="EventHandler" { // Log it log.warn( "InvalidHTTPMethod Execution of (#arguments.faultAction#): #arguments.event.getHTTPMethod()#", - getHTTPRequestData() + getHTTPRequestData( false ) ); // Setup Response diff --git a/system/aop/Matcher.cfc b/system/aop/Matcher.cfc index c5f950848..90fc44ca8 100644 --- a/system/aop/Matcher.cfc +++ b/system/aop/Matcher.cfc @@ -50,7 +50,7 @@ component accessors="true"{ /** * Constructor */ - function init(){ + function init(){ reset(); return this; } @@ -58,7 +58,7 @@ component accessors="true"{ /** * Reset the matcher memento to defaults */ - function reset(){ + function reset(){ // prepare instance for this matcher variables.any = false; variables.returns = ""; @@ -67,14 +67,14 @@ component accessors="true"{ variables.instanceOf = ""; variables.regex = ""; variables.methods = ""; - + // Aggregators variables.and = ""; - variables.or = ""; - - return this; + variables.or = ""; + + return this; } - + /** * Get the matcher memento */ @@ -88,60 +88,60 @@ component accessors="true"{ } return memento; } - + /** * Matches a class to this matcher according to its criteria * @target The target to match against to * @mapping The target mapping to match against * @mapping.doc_generic coldbox.system.ioc.config.Mapping */ - boolean function matchClass( required target, required mapping ){ + boolean function matchClass( required target, required mapping ){ var results = matchClassRules( argumentCollection=arguments ); - + // AND matcher set? - if( isObject( variables.and ) ){ - return ( results AND variables.and.matchClass( argumentCollection=arguments ) ); + if( isObject( variables.and ) ){ + return ( results AND variables.and.matchClass( argumentCollection=arguments ) ); } // OR matcher set? - if( isObject( variables.or ) ){ - return ( results OR variables.or.matchClass( argumentCollection=arguments ) ); + if( isObject( variables.or ) ){ + return ( results OR variables.or.matchClass( argumentCollection=arguments ) ); } - - return results; + + return results; } - + /** * Matches a method to this matcher according to its criteria * @metadata The UDF metadata to use for matching */ - boolean function matchMethod( required metadata ){ + boolean function matchMethod( required metadata ){ var results = matchMethodRules( arguments.metadata ); - + // AND matcher set? - if( isObject( variables.and ) ){ - return ( results AND variables.and.matchMethod( arguments.metadata ) ); + if( isObject( variables.and ) ){ + return ( results AND variables.and.matchMethod( arguments.metadata ) ); } // OR matcher set? - if( isObject( variables.or ) ){ - return ( results OR variables.or.matchMethod( arguments.metadata ) ); + if( isObject( variables.or ) ){ + return ( results OR variables.or.matchMethod( arguments.metadata ) ); } - - return results; + + return results; } - + /** * Go through all the rules in this matcher and match * @metadata The UDF metadata to use for matching */ - private boolean function matchMethodRules( required metadata ){ + private boolean function matchMethodRules( required metadata ){ // Some metadata defaults var name = arguments.metadata.name; var returns = "any"; - - if( structKeyExists( arguments.metadata, "returntype" ) ){ - returns = arguments.metadata.returntype; + + if( structKeyExists( arguments.metadata, "returntype" ) ){ + returns = arguments.metadata.returntype; } - + // Start with any() if( variables.any ){ return true; } // Check explicit methods @@ -159,32 +159,32 @@ component accessors="true"{ // annotation if( len( variables.annotation ) AND structKeyExists( arguments.metadata, variables.annotation ) ){ // No annotation value - if( NOT structKeyExists( variables, "annotationValue" ) ){ - return true; + if( NOT structKeyExists( variables, "annotationValue" ) ){ + return true; } - + // check annotation value if( structKeyExists( variables, "annotationValue" ) AND arguments.metadata[ variables.annotation ] EQ variables.annotationValue ){ - return true; + return true; } } - - return false; + + return false; } - + /** * Go through all the rules in this matcher and match * @target The target to match against to * @mapping The target mapping to match against * @mapping.doc_generic coldbox.system.ioc.config.Mapping */ - private boolean function matchClassRules( required target, required mapping ){ + private boolean function matchClassRules( required target, required mapping ){ var md = arguments.mapping.getObjectMetadata(); var path = reReplace( md.name, "(\/|\\)", ".", "all" ); - + // Start with any() - if( variables.any ){ - return true; + if( variables.any ){ + return true; } // Check explicit mappings if( len( variables.mappings ) AND listFindNoCase( variables.mappings, arguments.mapping.getName() ) ){ @@ -201,42 +201,42 @@ component accessors="true"{ // annotation if( len( variables.annotation ) AND structKeyExists( md, variables.annotation ) ){ // No annotation value - if( NOT structKeyExists( variables, "annotationValue" ) ){ - return true; + if( NOT structKeyExists( variables, "annotationValue" ) ){ + return true; } - + // check annotation value if( structKeyExists( variables, "annotationValue" ) AND md[ variables.annotation ] EQ variables.annotationValue ){ - return true; + return true; } } - - return false; + + return false; } - + /** * Match against any method name or class path */ - function any(){ + function any(){ variables.any = true; return this; } - + /** * Match against return types in methods only * @type The type of return to match against. Only for method matching */ - function returns( required type ){ + function returns( required type ){ variables.returns = arguments.type; return this; } - + /** * Matches annotations on components or methods with or without a value * @annotation The annotation to discover * @value The value of the annotation that must match. OPTIONAL */ - function annotatedWith( required annotation, value ){ + function annotatedWith( required annotation, value ){ variables.annotation = arguments.annotation; // the value of the annotation if( structKeyExists( arguments, "value" ) ){ @@ -244,67 +244,67 @@ component accessors="true"{ } return this; } - + /** * Match one, list or array of mapping names. Class Matching Only. * @mappings One, list or array of mappings to match */ - function mappings( required mappings ){ - if( isArray( arguments.mappings ) ){ - arguments.mappings = arrayToList( arguments.mappings ); + function mappings( required mappings ){ + if( isArray( arguments.mappings ) ){ + arguments.mappings = arrayToList( arguments.mappings ); } variables.mappings = arguments.mappings; return this; } - + /** * Matches against a family of components according to the passed classPath. Class Matching Only. * @classPath The class path to verify instance of */ - function instanceOf( required classPath ){ + function instanceOf( required classPath ){ variables.instanceOf = arguments.classPath; return this; } - + /** * Matches a class path or method name to this regular expression * @regex The regular expression to match against */ - function regex( required regex ){ + function regex( required regex ){ variables.regex = arguments.regex; - return this; + return this; } - + /** * A list, one or an array of methods to explicitly match * @methods One, list or array of methods to match */ - function methods( required methods ){ - if( isArray( arguments.methods ) ){ - arguments.methods = arrayToList( arguments.methods ); + function methods( required methods ){ + if( isArray( arguments.methods ) ){ + arguments.methods = arrayToList( arguments.methods ); } variables.methods = arguments.methods; return this; } - + /** * AND this matcher with another matcher * @matcher The matcher to AND this matcher with * @matcher.doc_generic coldbox.system.aop.Matcher */ - function andMatch( required matcher ){ + function andMatch( required matcher ){ variables.and = arguments.matcher; return this; } - + /** * OR this matcher with another matcher * @matcher The matcher to AND this matcher with * @matcher.doc_generic coldbox.system.aop.Matcher */ - function orMatch( required matcher ){ + function orMatch( required matcher ){ variables.or = arguments.matcher; return this; } - + } \ No newline at end of file diff --git a/system/async/Future.cfc b/system/async/Future.cfc index 31bca953c..0cba0e66c 100644 --- a/system/async/Future.cfc +++ b/system/async/Future.cfc @@ -795,7 +795,7 @@ component accessors="true" { } /** - * This function can accept an array of items or a struct and apply a function + * This function can accept an array of items or a struct of items and apply a function * to each of the item's in parallel. The `fn` argument receives the appropriate item * and must return a result. Consider this a parallel map() operation * @@ -806,52 +806,98 @@ component accessors="true" { * allApply( data, ( item ) => item.key & item.value.toString() ) * * - * @items An array to process - * @fn The function that will be applied to each of the array's items + * @items An array or struct to process in parallel + * @fn The function that will be applied to each of the collection's items * @executor The custom executor to use if passed, else the forkJoin Pool + * @timeout The timeout to use when waiting for each item to be processed + * @timeUnit The time unit to use, available units are: days, hours, microseconds, milliseconds, minutes, nanoseconds, and seconds. The default is milliseconds * - * @return An array with the items processed + * @throws UnsuportedCollectionException - When something other than an array or struct is passed as items + * @return An array or struct with the items processed in parallel */ - any function allApply( any items, required fn, executor ){ - var incomingExecutor = arguments.executor ?: ""; + any function allApply( items, fn, executor, timeout, timeUnit ){ + var incomingExecutor = arguments.executor ?: ""; + // Boolean indicator to avoid `isObject()` calls on iterations + var usingExecutor = isObject( incomingExecutor ); + // Cast it here, so it only happens once + var currentTimeout = javacast( "long", arguments.timeout ?: variables.futureTimeout.timeout ); + var currentTimeunit = arguments.timeUnit ?: variables.futureTimeout.timeUnit; + + // Create the function proxy once instead of many times during iterations + var jApply = createDynamicProxy( + new proxies.Function( + arguments.fn, + variables.debug, + variables.loadAppContext + ), + [ "java.util.function.Function" ] + ); // Array Processing if( isArray( arguments.items ) ){ + // Return the array as a collection of processed values return arguments.items + // Startup the tasks .map( function( thisItem ){ - if ( isObject( incomingExecutor ) ) { - return new Future( thisItem ).thenAsync( fn, incomingExecutor ); + // Create a new completed future + var f = new Future( arguments.thisItem ); + + // Execute it on a custom executor or core + if( usingExecutor ){ + return f.setNative( + f.getNative().thenApplyAsync( jApply, incomingExecutor ) + ); } - return new Future( thisItem ).thenAsync( fn ); + + // Core Executor + return f.setNative( + f.getNative().thenApplyAsync( jApply ) + ); } ) + // Collect the tasks .map( function( thisFuture ){ return arguments.thisFuture.get( - javacast( "long", variables.futureTimeout.timeout ), - arguments.thisFuture.$timeUnit.get( variables.futureTimeout.timeUnit ) + currentTimeout, + currentTimeunit ); } ); } // Struct Processing else if( isStruct( arguments.items ) ){ return arguments.items + // Startup the tasks .map( function( key, value ){ - if ( isObject( incomingExecutor ) ) { - return new Future( { - "key" : arguments.key, - "value" : arguments.value - } ).thenAsync( fn, incomingExecutor ); - } - return new Future( { - "key" : arguments.key, + // Create a new completed future + var f = new Future( { + "key" : arguments.key, "value" : arguments.value - } ).thenAsync( fn ); + } ); + + // Execute it on a custom executor or core + if( usingExecutor ){ + return f.setNative( + f.getNative().thenApplyAsync( jApply, incomingExecutor ) + ); + } + + // Core Executor + return f.setNative( + f.getNative().thenApplyAsync( jApply ) + ); } ) + // Collect the tasks .map( function( key, thisFuture ){ return arguments.thisFuture.get( - javacast( "long", variables.futureTimeout.timeout ), - arguments.thisFuture.$timeUnit.get( variables.futureTimeout.timeUnit ) + currentTimeout, + currentTimeunit ); } ); + } else{ + throw( + message : "The collection type passed is not yet supported!", + type : "UnsuportedCollectionException", + detail : getMetadata( arguments.items ) + ); } } diff --git a/system/async/proxies/BiFunction.cfc b/system/async/proxies/BiFunction.cfc index 8be3f7a11..0e86729d4 100644 --- a/system/async/proxies/BiFunction.cfc +++ b/system/async/proxies/BiFunction.cfc @@ -23,8 +23,8 @@ component extends="BaseProxy" { try { lock name="#getConcurrentEngineLockName()#" type="exclusive" timeout="60" { return variables.target( - arguments.t ?: javaCast( "null", "" ), - arguments.u ?: javaCast( "null", "" ) + isNull( arguments.t ) ? javaCast( "null", "" ) : arguments.t, + isNull( arguments.u ) ? javaCast( "null", "" ) : arguments.u ); } } finally { diff --git a/system/cache/IColdboxApplicationCache.cfc b/system/cache/IColdboxApplicationCache.cfc index 3e53c52f6..6313a6ca8 100644 --- a/system/cache/IColdboxApplicationCache.cfc +++ b/system/cache/IColdboxApplicationCache.cfc @@ -20,21 +20,21 @@ Description : - + - + - + - + @@ -61,7 +61,7 @@ Description : - + \ No newline at end of file diff --git a/system/cache/report/skins/default/CacheReport.cfm b/system/cache/report/skins/default/CacheReport.cfm index 511f9d2c2..37083e1b9 100644 --- a/system/cache/report/skins/default/CacheReport.cfm +++ b/system/cache/report/skins/default/CacheReport.cfm @@ -40,7 +40,7 @@ Last Reap
- #DateFormat(cacheStats.getlastReapDatetime(),"MMM-DD-YYYY")# + #DateFormat(cacheStats.getlastReapDatetime(),"mmm-dd-yyyy")# #TimeFormat(cacheStats.getlastReapDatetime(),"hh:mm:ss tt")#
@@ -125,4 +125,4 @@ #renderCacheContentReport(arguments.cacheName)# - \ No newline at end of file + diff --git a/system/cache/util/EventURLFacade.cfc b/system/cache/util/EventURLFacade.cfc index 2dfd4ae45..b1880305a 100644 --- a/system/cache/util/EventURLFacade.cfc +++ b/system/cache/util/EventURLFacade.cfc @@ -13,6 +13,7 @@ component accessors="true"{ /** * Constructor + * * @cacheProvider Provider to connect to */ function init( required cacheProvider ){ @@ -20,6 +21,16 @@ component accessors="true"{ return this; } + /** + * Build an app link via the request context object + */ + string function buildAppLink(){ + return variables.cacheProvider.getColdBox() + .getRequestService() + .getContext() + .getSesBaseUrl(); + } + /** * Build a unique hash from an incoming request context * @@ -36,7 +47,7 @@ component accessors="true"{ // Get the original incoming context hash "incomingHash" = incomingHash, // Multi-Host support - "cgihost" = CGI.SERVER_NAME + "cgihost" = arguments.event.getSesBaseUrl() }; // Incorporate Routed Structs @@ -65,7 +76,7 @@ component accessors="true"{ // Get the original incoming context hash according to incoming arguments "incomingHash" = hash( virtualRC.toString() ), // Multi-Host support - "cgihost" = CGI.SERVER_NAME + "cgihost" = buildAppLink() }; // return hash from cache key struct @@ -103,5 +114,4 @@ component accessors="true"{ return variables.cacheProvider.getEventCacheKeyPrefix() & arguments.targetEvent & "-" & arguments.keySuffix & "-"; } - } \ No newline at end of file diff --git a/system/cache/util/ICacheStats.cfc b/system/cache/util/ICacheStats.cfc index aceb34b71..04c64f451 100644 --- a/system/cache/util/ICacheStats.cfc +++ b/system/cache/util/ICacheStats.cfc @@ -84,7 +84,6 @@ DEPRECATED USE IStats.cfc instead returntype="any" output ="false" hint ="Get the total cache's misses" - colddoc >
diff --git a/system/exceptions/BugReport.cfm b/system/exceptions/BugReport.cfm index 193d95fb2..09d762119 100644 --- a/system/exceptions/BugReport.cfm +++ b/system/exceptions/BugReport.cfm @@ -68,7 +68,7 @@ A reporting template about exceptions in your ColdBox Apps Timestamp: - #dateformat(now(), "MM/DD/YYYY")# #timeformat(now(),"hh:MM:SS TT")# + #dateformat(now(), "mm/dd/yyyy")# #timeformat(now(),"hh:mm:ss tt")# Event Details: @@ -148,14 +148,14 @@ A reporting template about exceptions in your ColdBox Apps

Stack Trace:

-
#processStackTrace( oException.getstackTrace() )#
+
#oException.processStackTrace( oException.getstackTrace() )#

FRAMEWORK SNAPSHOT:

- + @@ -190,7 +190,7 @@ A reporting template about exceptions in your ColdBox Apps - + diff --git a/system/exceptions/Whoops.cfm b/system/exceptions/Whoops.cfm index e9719b3df..14eaa7906 100644 --- a/system/exceptions/Whoops.cfm +++ b/system/exceptions/Whoops.cfm @@ -2,12 +2,14 @@ // Detect Session Scope local.sessionScopeExists = getApplicationMetadata().sessionManagement; + // Detect host try { local.thisInetHost = createObject( "java", "java.net.InetAddress" ).getLocalHost().getHostName(); } catch ( any e ) { local.thisInetHost = "localhost"; } + // Build event details local.eventDetails = { "Error Code" : ( oException.getErrorCode() != 0 ) ? oException.getErrorCode() : "", @@ -51,7 +53,7 @@ "Coldfusion ID" : "Session Scope Not Enabled", "Template Path" : CGI.CF_TEMPLATE_PATH, "Path Info" : CGI.PATH_INFO, - "Host" : CGI.SERVER_NAME, + "Host" : CGI.HTTP_HOST, "Server" : local.thisInetHost, "Query String" : CGI.QUERY_STRING, "Referrer" : CGI.HTTP_REFERER, @@ -102,9 +104,28 @@ }; } + // Get exception information and mark the safe environment token local.e = oException.getExceptionStruct(); stackFrames = arrayLen( local.e.TagContext ); local.safeEnvironment = "development"; + + // Is this an Ajax Request? If so, present the plain exception templates + local.requestHeaders = getHTTPRequestData( false ).headers; + if( + structKeyExists( local.requestHeaders, "X-Requested-With" ) + && + local.requestHeaders[ "X-Requested-With" ] eq "XMLHttpRequest" + ){ + // Development report + if( local.eventDetails.environment eq local.safeEnvironment ){ + include "BugReport.cfm"; + } + // Production Report + else { + include "BugReport-Public.cfm"; + } + return; + } @@ -120,7 +141,7 @@ SyntaxHighlighter.defaults[ 'gutter' ] = true; SyntaxHighlighter.defaults[ 'smart-tabs' ] = false; SyntaxHighlighter.defaults[ 'tab-size' ] = 4; - SyntaxHighlighter.all(); + //SyntaxHighlighter.all(); @@ -337,7 +358,7 @@
- #oException.displayScope( getHTTPRequestData().headers )# + #oException.displayScope( local.requestHeaders )#
@@ -371,7 +392,7 @@ style="cursor: pointer"> -
#processStackTrace( oException.getstackTrace() )#
+
#oException.processStackTrace( oException.getstackTrace() )#
@@ -380,24 +401,45 @@ - + - - - - + + + + + + + + + + + + + + + + + @@ -406,6 +448,7 @@ diff --git a/system/exceptions/js/whoops.js b/system/exceptions/js/whoops.js index a5e065bec..00691ce12 100644 --- a/system/exceptions/js/whoops.js +++ b/system/exceptions/js/whoops.js @@ -29,16 +29,35 @@ function toggleActiveClasses( id ) { } function changeCodePanel( id ) { + // activate the stackframe toggleActiveClasses( id ); - var code = document.getElementById( id + "-code" ); - var highlightLine = code.getAttribute( "data-highlight-line" ); + // get access to pre tag so we can populate and highlight it + var code = document.getElementById( id + "-code" ); + // get access to pre > script tag so we can populate it with template code when needed + var codeScript = document.getElementById( id + "-script" ); + // Get the template id for inejcting the source + var templateSource = document.getElementById( "stackframe-" + code.getAttribute( "data-template-id" ) ); + // Only assign sources if codeContainer exists, else ignore. if( codeContainer == null ){ return; } + // Activate highlighting only if template not rendered already + if( code.getAttribute( "data-template-rendered" ) == "false" ){ + // Inject template source into highlighter source + codeScript.innerHTML = templateSource.innerHTML; + codeScript.setAttribute( "type", "syntaxhighlighter" ); + // Activate it + SyntaxHighlighter.highlight( {}, id + "-script" ); + // Mark as rendered + code.setAttribute( "data-template-rendered", "true" ); + } + + // Inject the source to the code container for visualizations codeContainer.innerHTML = code.innerHTML; - scrollToLine( highlightLine ); + // Scroll to the highlighted line + scrollToLine( code.getAttribute( "data-highlight-line" ) ); } function reinitframework( usingPassword ){ @@ -103,6 +122,8 @@ Array.from( document.querySelectorAll( ".stacktrace" ) ) } ); document.addEventListener( "DOMContentLoaded", function() { - var initialStackTrace = document.querySelector( ".stacktrace__list .stacktrace" ); - setTimeout(function(){ changeCodePanel( initialStackTrace.id ); }, 500); + var initialStackTrace = document.querySelector( ".stacktrace__list .stacktrace" ); + setTimeout( function(){ + changeCodePanel( initialStackTrace.id ); + }, 500 ); } ); diff --git a/system/ioc/Injector.cfc b/system/ioc/Injector.cfc index bd89b77d0..3b26f9373 100644 --- a/system/ioc/Injector.cfc +++ b/system/ioc/Injector.cfc @@ -256,13 +256,18 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I variables.eventManager.registerInterceptor( interceptorObject=variables.binder, interceptorName="wirebox-binder" ); } - // Check if binder has onLoad convention - if( structKeyExists( variables.binder, "onLoad" ) ){ - variables.binder.onLoad(); + // process mappings for metadata and initialization. + if( variables.binder.getAutoProcessMappings() ){ + variables.binder.processMappings(); } - // process mappings for metadata and initialization. - //variables.binder.processMappings(); + // Process Eager Inits + variables.binder.processEagerInits(); + + // Check if binder has onLoad convention and execute callback + if( structKeyExists( variables.binder, "onLoad" ) ){ + variables.binder.onLoad( this ); + } // Announce To Listeners we are online iData.injector = this; @@ -284,22 +289,23 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I if( variables.log.canInfo() ){ variables.log.info( "Shutdown of Injector: #getInjectorID()# requested and started." ); } + // Notify Listeners variables.eventManager.announce( "beforeInjectorShutdown", iData ); // Check if binder has onShutdown convention if( structKeyExists( variables.binder, "onShutdown" ) ){ - variables.binder.onShutdown(); + variables.binder.onShutdown( this ); } // Is parent linked if( isObject( variables.parent ) ){ - variables.parent.shutdown(); + variables.parent.shutdown( this ); } // standalone cachebox? Yes, then shut it down baby! if( isCacheBoxLinked() ){ - variables.cacheBox.shutdown(); + variables.cacheBox.shutdown( this ); } // Remove from scope @@ -313,6 +319,11 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I variables.log.info( "Shutdown of injector: #getInjectorID()# completed." ); } + // Shutdown LogBox last if not in ColdBox Mode + if( !isColdBoxLinked() ){ + variables.logBox.shutdown(); + } + return this; } diff --git a/system/ioc/Scopes.cfc b/system/ioc/Scopes.cfc index cf5a1f906..ec4330c76 100644 --- a/system/ioc/Scopes.cfc +++ b/system/ioc/Scopes.cfc @@ -18,6 +18,7 @@ component{ /** * Verify if an incoming scope is valid + * * @scope The scope to check */ boolean function isValidScope( required scope ){ diff --git a/system/ioc/config/Binder.cfc b/system/ioc/config/Binder.cfc index 1063606b1..b3a6aafe0 100644 --- a/system/ioc/config/Binder.cfc +++ b/system/ioc/config/Binder.cfc @@ -1,1121 +1,1389 @@ - - - - - // Available WireBox public scopes - this.SCOPES = createObject("component","coldbox.system.ioc.Scopes"); - // Available WireBox public types - this.TYPES = createObject("component","coldbox.system.ioc.Types"); - // Utility class - this.UTILITY = createObject("component","coldbox.system.core.util.Util"); +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * This is the WireBox configuration Binder. You use it to configure an injector instance. + * This binder will hold all your object mappings and injector settings. + */ +component accessors="true" { + + /** + * The current mapping pointer for DSL configurations + */ + property name="currentMapping" type="array"; + + /** + * The configuration properties/settings for the app context or the injector context (standalone) + */ + property name="properties" type="struct"; + + /** + * The injector reference this binder is bound to + */ + property name="injector"; + + /** + * The ColdBox reference this binder is bound to, this can be null + */ + property name="coldbox"; + + /** + * Main WireBox configuration structure + */ + property name="wirebox" type="struct"; + + /** + * Main CacheBox configuration structure + */ + property name="cachebox" type="struct"; + + /** + * The configuration CFC for this binder + */ + property name="config"; + + /** + * The shortcut application mapping string + */ + property name="appMapping"; + + /** + * The LogBox config file + */ + property name="logBoxConfig"; + + /** + * The Listeners + */ + property name="listeners" type="array"; + + /** + * The scope registration config + */ + property name="scopeRegistration" type="struct"; + + /** + * The custom DSL namespaces + */ + property name="customDSL" type="struct"; + + /** + * The custom scopes + */ + property name="customScopes" type="struct"; + + /** + * The scan locations for this binder + */ + property name="scanLocations" type="struct"; + + /** + * The collection of mappings + */ + property name="mappings" type="struct"; + + /** + * The aspects binded to mappings + */ + property name="aspectBindings" type="array"; + + /** + * The parent injector mapping + */ + property name="parentInjector"; + + /** + * The stop recursions for the binder + */ + property name="stopRecursions" type="array"; + + /** + * The metadata cache for this binder + */ + property name="metadataCache"; + + /** + * Boolean indicator if on startup all mappings will be processed for metdata inspections or + * lazy loaded. We default to lazy load due to performance. + */ + property name="autoProcessMappings" type="boolean"; + + /** + * The configuration DEFAULTS struct + */ + property + name ="DEFAULTS" + setter="false" + type ="struct"; + + /** + * -------------------------------------------------- + * Binder Public References + * -------------------------------------------------- + * One day move as static references + */ + + // Available WireBox public scopes + this.SCOPES = new coldbox.system.ioc.Scopes(); + // Available WireBox public types + this.TYPES = new coldbox.system.ioc.Types(); + + // WireBox Operational Defaults + variables.DEFAULTS = { + // LogBox Default Config + logBoxConfig : "coldbox.system.ioc.config.LogBox", + // Scope Registration + scopeRegistration : { enabled : true, scope : "application", key : "wireBox" }, + // CacheBox Integration Defaults + cacheBox : { + enabled : false, + configFile : "", + cacheFactory : "", + classNamespace : "coldbox.system.cache" + }, + // Auto process mappings on startup + // We lazy process all mappings until requested + autoProcessMappings : false + }; + + // Startup the configuration + reset(); + + /** + * Constructor + * + * @injector The injector this binder is bound to + * @config The WireBox Injector Data Configuration CFC instance or instantiation path to it. Leave blank if using this configuration object programatically + * @properties A structure of binding properties to passthrough to the Binder Configuration CFC + */ + function init( + required injector, + config, + struct properties = {} + ){ + // Setup incoming properties + variables.properties = arguments.properties; + // Setup Injector this binder is bound to. + variables.injector = arguments.injector; + // ColdBox Context binding if any? + variables.coldbox = variables.injector.getColdBox(); + // is coldbox linked + if ( isObject( variables.coldbox ) ) { + variables.appMapping = variables.coldbox.getSetting( "AppMapping" ); + } + + // If Config CFC sent and a path, then create the data CFC + if ( !isNull( arguments.config ) and isSimpleValue( arguments.config ) ) { + arguments.config = createObject( "component", arguments.config ); + } + + // If sent and a data CFC variables + if ( !isNull( arguments.config ) and isObject( arguments.config ) ) { + // Decorate our data CFC + arguments.config.getPropertyMixin = variables.injector.getUtil().getMixerUtil().getPropertyMixin; + // Execute the configuration + arguments.config.configure( this ); + // Load the raw data DSL + loadDataDSL( arguments.config.getPropertyMixin( "wireBox", "variables", {} ) ); + } + + return this; + } + + /** + * The main configuration method that must be overriden by a specific WireBox Binder configuration object + */ + function configure(){ + // Implemented by concrete classes + } + + /** + * Reset the configuration back to the original binder defaults + */ + Binder function reset(){ // Contains the mappings currently being affected by the DSL. - currentMapping = []; - // Instance private scope - instance = {}; - // WireBox Defaults - DEFAULTS = { - //LogBox Defaults - logBoxConfig = "coldbox.system.ioc.config.LogBox", - // Scope Defaults - scopeRegistration = { - enabled = true, - scope = "application", - key = "wireBox" - }, - // CacheBox Integration Defaults - cacheBox = { - enabled = false, - configFile = "", - cacheFactory = "", - classNamespace = "coldbox.system.cache" - } - }; - // Startup the configuration - reset(); - - - - - - - - - // Setup incoming properties - instance.properties = arguments.properties; - // Setup Injector this binder is bound to. - instance.injector = arguments.injector; - // ColdBox Context binding if any? - instance.coldbox = instance.injector.getColdBox(); - // is coldbox linked - if( isObject(instance.coldbox) ){ - variables.appMapping = instance.coldbox.getSetting("AppMapping"); - } - - // If sent and a path, then create the data CFC - if( structKeyExists(arguments, "config") and isSimpleValue(arguments.config) ){ - arguments.config = createObject("component",arguments.config); - } - - // If sent and a data CFC instance - if( structKeyExists(arguments,"config") and isObject(arguments.config) ){ - // Decorate our data CFC - arguments.config.getPropertyMixin = this.utility.getMixerUtil().getPropertyMixin; - // Execute the configuration - arguments.config.configure(this); - // Load the raw data DSL - loadDataDSL( arguments.config.getPropertyMixin("wireBox","variables",structnew()) ); - } - - return this; - - - - - - - - - - - - - - - - - - - - - - - - - - - // Main wirebox structure - variables.wirebox = {}; - // logBox File - instance.logBoxConfig = DEFAULTS.logBoxConfig; - // CacheBox integration - instance.cacheBox = DEFAULTS.cacheBox; - // Listeners - instance.listeners = []; - // Scope Registration - instance.scopeRegistration = DEFAULTS.scopeRegistration; - // Custom DSL namespaces - instance.customDSL = {}; - // Custom Storage Scopes - instance.customScopes = {}; - // Package Scan Locations - instance.scanLocations = createObject("java","java.util.LinkedHashMap").init(5); - // Object Mappings - instance.mappings = {}; - // Aspect Bindings - instance.aspectBindings = []; - // Parent Injector Mapping - instance.parentInjector = ""; - // Binding Properties - instance.properties = {}; - // Stop Recursion classes - instance.stopRecursions = []; - // Meatadata cache - instance.metadataCache = ''; - - - - - - - - - - - - - - - - - - - - - - - if( propertyExists(arguments.name) ){ - return instance.properties[arguments.name]; - } - if( structKeyExists(arguments,"default") ){ - return arguments.default; - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - instance.mappings[ arguments.name ] = arguments.mapping; - - - - - - - - structDelete( instance.mappings, arguments.name ); - - - - - - - - - - - - - - - - - - - var cName = listlast( arguments.path, "." ); - - if( arguments.prepend ){ - cName = arguments.namespace & cName; - } else { - cName = cName & arguments.namespace; - } - - // directly map to a path - return map( cName, arguments.force ).to( arguments.path ); - - - - - - - - - - - - - - - var directory = expandPath( "/#replace( arguments.packagePath, ".", "/", "all" )#" ); - var qObjects = ""; - var thisTargetPath = ""; - var tmpCurrentMapping = []; - - // Clear out any current mappings - currentMapping = []; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - for( var mapping in getCurrentMapping() ) { - mapping.process(); - } - return this; - - - - - - - - - // Clear out any current mappings - currentMapping = []; - - // generate mapping entry for this dude. - var name = ""; - var x = 1; - - // unflatten list - if( isSimpleValue( arguments.alias ) ){ arguments.alias = listToArray(arguments.alias); } - - // first entry - name = arguments.alias[1]; - - // check if mapping exists, if so, just use and return. - if( structKeyExists( instance.mappings, name) and !arguments.force ){ - arrayAppend( currentMapping, instance.mappings[ name ] ); - return this; - } - - // generate the mapping for the first name passed - instance.mappings[ name ] = createObject("component","coldbox.system.ioc.config.Mapping").init( name ); - - // set the current mapping - arrayAppend( currentMapping, instance.mappings[ name ] ); - - // Set aliases, scopes and types - instance.mappings[ name ] - .setAlias( arguments.alias ) - .setType( this.TYPES.CFC ); - - // Loop and create alias references - for(x=2;x lte arrayLen(arguments.alias); x++){ - instance.mappings[ arguments.alias[x] ] = instance.mappings[ name ]; - } - - return this; - - - - - - - - for( var mapping in getCurrentMapping() ) { - mapping.setPath( arguments.path ).setType( this.TYPES.CFC ); - } - return this; - - - - - - - - // copy parent class's memento instance, exclude alias, name and path - for( var mapping in getCurrentMapping() ) { - mapping.processMemento( getMapping( arguments.alias ).getMemento(), "alias,name" ); - } - return this; - - - - - - - - - for( var mapping in getCurrentMapping() ) { - mapping.setType( this.TYPES.FACTORY ).setPath( arguments.factory ).setMethod( arguments.method ); - } - return this; - - - - - - - - - - - - for( var mapping in getCurrentMapping() ) { - mapping.addDIMethodArgument(argumentCollection=arguments); - } - return this; - - - - - - - - for( var mapping in getCurrentMapping() ) { - mapping.setPath( arguments.path ).setType( this.TYPES.JAVA ); - } - return this; - - - - - - - - for( var mapping in getCurrentMapping() ) { - mapping.setPath( arguments.path ).setType( this.TYPES.WEBSERVICE ); - } - return this; - - - - - - - - for( var mapping in getCurrentMapping() ) { - mapping.setPath( arguments.path ).setType( this.TYPES.RSS ); - } - return this; - - - - - - - - for( var mapping in getCurrentMapping() ) { - mapping.setDSL( arguments.dsl ).setType( this.TYPES.DSL ); - } - return this; - - - - - - - - for( var mapping in getCurrentMapping() ) { - mapping.setPath( arguments.provider ).setType( this.TYPES.PROVIDER ); - } - return this; - - - - - - - - for( var mapping in getCurrentMapping() ) { - mapping.setValue( arguments.value ).setType( this.TYPES.CONSTANT ); - } - return this; - - - - - - - - for( var mapping in getCurrentMapping() ) { - mapping.setConstructor( arguments.constructor ); - } - return this; - - - - - - - var key = ""; - for(key in arguments){ - for( var mapping in getCurrentMapping() ) { - mapping.addDIConstructorArgument(name=key,value=arguments[key]); + variables.currentMapping = []; + // Main wirebox structure + variables.wirebox = {}; + // logBox File + variables.logBoxConfig = variables.DEFAULTS.logBoxConfig; + // CacheBox integration + variables.cacheBox = variables.DEFAULTS.cacheBox; + // Scope Registration + variables.scopeRegistration = variables.DEFAULTS.scopeRegistration; + // Custom DSL namespaces + variables.customDSL = {}; + // Custom Storage Scopes + variables.customScopes = {}; + // Package Scan Locations + variables.scanLocations = structNew( "ordered" ); + // Parent Injector Mapping + variables.parentInjector = ""; + // Stop Recursion classes + variables.stopRecursions = []; + // Listeners + variables.listeners = []; + // Object Mappings + variables.mappings = {}; + // Aspect Bindings + variables.aspectBindings = []; + // Binding Properties + variables.properties = {}; + // Meatadata cache + variables.metadataCache = ""; + // Auto Process Mappings + variables.autoProcessMappings = variables.DEFAULTS.autoProcessMappings; + + return this; + } + + /** + * -------------------------------------------------- + * Binder Property Binding Methods + * -------------------------------------------------- + */ + + /** + * Get a binded property. If not found it will try to return the default value passed, else it returns an exception + * + * @name The name of the property to get + * @defaultValue The default value if property is not found + * + * @throws PropertyNotFoundException - If the property is not found and no default sent + * + * @return Property value + */ + function getProperty( required name, defaultValue ){ + // Prop Check + if ( structKeyExists( variables.properties, arguments.name ) ) { + return variables.properties[ arguments.name ]; + } + + // TODO: remove by v7 + // Deprecated Check + if ( !isNull( arguments.default ) ) { + return arguments.default; + } + + // Default Value + if ( !isNull( arguments.defaultValue ) ) { + return arguments.defaultValue; + } + + // Throw exception + throw( + message = "The property requested #arguments.name# was not found", + detail = "Properties defined are #structKeyList( variables.properties )#", + type = "PropertyNotFoundException" + ); + } + + /** + * Create a new binding property + * + * @name The name of the property to set + * @value The value of the property + */ + Binder function setProperty( required name, required value ){ + variables.properties[ arguments.name ] = arguments.value; + return this; + } + + /** + * Verify if a property exists + * + * @name The name of the property to verify + */ + Boolean function propertyExists( required name ){ + return structKeyExists( variables.properties, arguments.name ); + } + + /** + * -------------------------------------------------- + * Binder Mapping Methods + * -------------------------------------------------- + */ + + /** + * Get a specific object mapping + * + * @name The name of the mapping + * + * @throws MappingNotFoundException - If the named mapping has not been registered + * @return coldbox.system.ioc.config.Mapping + */ + Mapping function getMapping( required name ){ + if ( structKeyExists( variables.mappings, arguments.name ) ) { + return variables.mappings[ arguments.name ]; + } + + throw( + message = "Mapping #arguments.name# has not been registered", + detail = "Registered mappings are: #structKeyList( variables.mappings )#", + type = "MappingNotFoundException" + ); + } + + /** + * Set a mapping object into the mappings map + * + * @name The name of the mapping + * @mapping The mapping object to register + */ + Binder function setMapping( required name, required mapping ){ + variables.mappings[ arguments.name ] = arguments.mapping; + return this; + } + + /** + * Destroys a registered mapping by name + * + * @name The name of the mapping + * + * @return A boolean indicator if the mapping was removed or not + */ + boolean function unMap( required name ){ + return structDelete( variables.mappings, arguments.name ); + } + + /** + * Verifies if a mapping exists in this binder or not + * + * @name The name of the mapping + */ + boolean function mappingExists( required name ){ + return structKeyExists( variables.mappings, arguments.name ); + } + + /** + * -------------------------------------------------- + * Binder Mapping DSL methods + * -------------------------------------------------- + */ + + /** + * Directly map to a path by using the last part of the path as the alias. + * This is equivalent to map('MyService').to('model.MyService'). + * Only use if the name of the alias is the same as the last part of the path. + * + * @path The class path to the object to map + * @namespace Provide namespace to merge it in + * @prepend Where to attach the namespace, at the beginning of the name or end of the name. Defaults to end of name + * @force Forces the registration of the mapping in case it already exists + */ + function mapPath( + required path, + namespace = "", + boolean prepend = false, + boolean force = false + ){ + var cName = listLast( arguments.path, "." ); + + if ( arguments.prepend ) { + cName = arguments.namespace & cName; + } else { + cName = cName & arguments.namespace; + } + + // directly map to a path + return map( cName, arguments.force ).to( arguments.path ); + } + + /** + * Maps an entire instantiation path directory, please note that the unique name of each file will be used and also processed for alias inspection + * + * @packagePath The instantiation packagePath to map + * @include An include regex that if matches will only include CFCs that match this case insensitive regex + * @exclude An exclude regex that if matches will exclude CFCs that match this case insensitive regex + * @influence The influence closure or UDF that will receive the currently working mapping so you can influence it during the iterations + * @filter The filter closure or UDF that will receive the path of the CFC to process and returns TRUE to continue processing or FALSE to skip processing + * @namespace Provide namespace to merge it in + * @prepend Where to attach the namespace, at the beginning of the name or end of the name. Defaults to end of name + * @process If true, all mappings discovered will be automatically processed for metdata and inspections. Default is false, everything lazy loads + * + * @throws DirectoryNotFoundException - If the requested package path does not exist. + */ + Binder function mapDirectory( + required packagePath, + include = "", + exclude = "", + influence, + filter, + namespace = "", + boolean prepend = false, + boolean process = false + ){ + // check directory exists + var targetDirectory = expandPath( "/#replace( arguments.packagePath, ".", "/", "all" )#" ); + if ( NOT directoryExists( targetDirectory ) ) { + throw( + message = "Directory does not exist", + detail = "Directory: #targetDirectory#", + type = "DirectoryNotFoundException" + ); + } + + // Clear out any current mappings + variables.currentMapping = []; + + // Scan + Process Objects + directoryList( + targetDirectory, // path + true, // recurse + "path", // list info + "*.cfc" // filter + ) + // Skip hidden files/dirs and also paths not in the include/exclude lists + .filter( function( thisPath ){ + // Skip hidden dirs (like .Appledouble) + if( left( arguments.thisPath, 1 ) EQ "." ){ + return false; } - } - return this; - - - - - - - for( var mapping in getCurrentMapping() ) { - mapping.setAutoInit( false ); - } - return this; - - - - - - - - for( var thisMapping in getCurrentMapping() ) { - thisMapping.setVirtualInheritance( mapping ); - } - return this; - - - - - - - for( var mapping in getCurrentMapping() ) { - mapping.setEagerInit( true ); - } - return this; - - - - - - - for( var mapping in getCurrentMapping() ) { - mapping.setAutowire( false ); - } - return this; - - - - - - - - if( mappingExists(arguments.alias) ){ - currentMapping = []; - currentMapping[ 1 ] = instance.mappings[arguments.alias]; - return this; - } - throw(message="The mapping '#arguments.alias# has not been initialized yet.'", - detail="Please use the map('#arguments.alias#') first to start working with a mapping", - type="Binder.InvalidMappingStateException"); - - - - - - - - - - - - - for( var mapping in getCurrentMapping() ) { - mapping.addDIConstructorArgument(argumentCollection=arguments); - } - return this; - - - - - - - - - - - - - for( var mapping in getCurrentMapping() ) { - mapping.addDISetter(argumentCollection=arguments); - } - return this; - - - - - - - - - - - - - - for( var mapping in getCurrentMapping() ) { - mapping.addDIProperty(argumentCollection=arguments); - } - return this; - - - - - - - - //inflate list - if( isSimpleValue(arguments.methods) ){ arguments.methods = listToArray(arguments.methods); } - // store list - for( var mapping in getCurrentMapping() ) { - mapping.setOnDIComplete( arguments.methods ); - } - return this; - - - - - - - - - for( var thisMapping in getCurrentMapping() ) { - thisMapping.addProviderMethod(argumentCollection=arguments); - } - return this; - - - - - - - - // check if invalid scope - if( NOT this.SCOPES.isValidScope(arguments.scope) AND NOT structKeyExists(instance.customScopes,arguments.scope) ){ - throw( message="Invalid WireBox Scope: '#arguments.scope#'", - detail="Please make sure you are using a valid scope, valid scopes are: #arrayToList(this.SCOPES.getValidScopes())# AND custom scopes: #structKeyList(instance.customScopes)#", - type="Binder.InvalidScopeMapping" ); - } - for( var mapping in getCurrentMapping() ) { - mapping.setScope( arguments.scope ); - } - return this; - - - - - - - return this.into( this.SCOPES.SINGLETON ); - - - - - - - for( var mapping in getCurrentMapping() ) { - mapping.setThreadSafe( true ); - } - return this; - - - - - - - for( var mapping in getCurrentMapping() ) { - mapping.setThreadSafe( false ); - } - return this; - - - - - - - - for( var mapping in getCurrentMapping() ) { - mapping.setInfluenceClosure( arguments.influenceClosure ); - } - return this; - - - - - - - - for( var mapping in getCurrentMapping() ) { - mapping.setExtraAttributes( arguments.data ); - } - return this; - - - - - - - - if( isSimpleValue( arguments.mixins ) ){ arguments.mixins = listToArray( arguments.mixins ); } - for( var mapping in getCurrentMapping() ) { - mapping.setMixins( arguments.mixins ); - } - return this; - - - - - - - - - - - - - - - // inflate incoming locations - if( isSimpleValue(arguments.classes) ){ arguments.classes = listToArray(arguments.classes); } - // Save them - instance.stopRecursions = arguments.classes; - - return this; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - var x = 1; - - // inflate incoming locations - if( isSimpleValue(arguments.locations) ){ arguments.locations = listToArray(arguments.locations); } - - // Prepare Locations - for(x=1; x lte arrayLen(arguments.locations); x++){ - // Validate it is not registered already - if ( NOT structKeyExists(instance.scanLocations, arguments.locations[x]) AND len(arguments.locations[x]) ){ - // Process creation path & Absolute Path - instance.scanLocations[ arguments.locations[x] ] = expandPath( "/" & replace(arguments.locations[x],".","/","all") & "/" ); + // If any of the following are true, then process it, else skip it + return ( + // We have an include list and the path matches + ( len( include ) AND reFindNoCase( include, arguments.thisPath ) ) + OR + // We have an exclude list and the path doesn't match + ( len( exclude ) AND NOT reFindNoCase( exclude, arguments.thisPath ) ) + // We have a closure filter, we ask the filter + OR + ( !isNull( filter ) AND filter( arguments.thisPath ) ) + OR + // No include, no exclude and no filter + ( NOT len( include ) AND NOT len( exclude ) AND isNull( filter ) ) + ); + } ) + // Transform the path to something usable for object creation + // leading slash, append package path, remove .cfc and /\ with . notation + .map( function( thisPath ){ + // Remove root directory from path to get relative path + arguments.thisPath = replaceNoCase( arguments.thisPath, targetDirectory, "" ); + // Process rest of manips + return reReplace( + reReplace( packagePath, "^/", "" ) & replaceNoCase( arguments.thisPath, ".cfc", "" ), + "(/|\\)", + ".", + "all" + ); + } ) + // Process the path + .each( function( thisPath ){ + // Backup the current array of mappings + var tmpCurrentMapping = variables.currentMapping; + + // Map the path + mapPath( + path : arguments.thisPath, + namespace : namespace, + prepend : prepend, + force : true + ); + + // Are we influencing? + if( !isNull( influence ) ){ + influence( this, arguments.thisPath, variables.currentMapping[ 1 ] ); } - } - - return this; - - - - - - - var x = 1; - - // inflate incoming locations - if( isSimpleValue(arguments.locations) ){ arguments.locations = listToArray(arguments.locations); } - - // Loop and remove - for(x=1;x lte arraylen(arguments.locations); x++ ){ - structDelete(instance.scanLocations, arguments.locations[x]); - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - for( var mapping in getCurrentMapping() ) { - // if key not passed, build a mapping name - if( NOT len(arguments.key) ){ - if( len( mapping.getPath() ) ){ - arguments.key = "wirebox-#mapping.getPath()#"; - } - else{ - arguments.key = "wirebox-#mapping.getName()#"; - } + /** + * Do this right away so aliases are picked up before this mapping potentially gets overwritten + * This is neccessary for multuple CFCs with the same name in different folders, but with unique aliases + * TODO: Move to async + */ + if( process ){ + variables.currentMapping[ 1 ].process( binder = this, injector = variables.injector ); } - // store the mapping info. - mapping.setScope( this.SCOPES.CACHEBOX ).setCacheProperties(argumentCollection=arguments); - - } + // Merge the full array of mappings back together + arrayAppend( tmpCurrentMapping, variables.currentMapping[ 1 ] ); + variables.currentMapping = tmpCurrentMapping; + } ); + return this; + } + + /** + * Auto process a mapping once defined, this is usually done if there are critical annotations that must be read upon startup, else avoid it and let them lazy load + */ + Binder function process(){ + variables.mappings.each( function( thisMapping ){ + arguments.thisMapping.process( binder : this, injector : variables.injector ); + } ); + return this; + } + + /** + * Create a mapping to an object + * + * @alias A single alias or a list or an array of aliases for this mapping. Remember an object can be refered by many names + * @force Forces the registration of the mapping in case it already exists + */ + Binder function map( required alias, boolean force = false ){ + // Clear out any current mappings + variables.currentMapping = []; + + // unflatten list + if ( isSimpleValue( arguments.alias ) ) { + arguments.alias = listToArray( arguments.alias ); + } + + // first entry + var name = arguments.alias[ 1 ]; + + // check if mapping exists, if so, just use and return. + if ( structKeyExists( variables.mappings, name ) and !arguments.force ) { + arrayAppend( variables.currentMapping, variables.mappings[ name ] ); return this; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - var wireBoxDSL = variables.wirebox; - var key = ""; - - // Coldbox Context Attached - if( isObject(instance.coldbox) ){ - // create scan location for model convention as the first one. - scanLocations( instance.coldbox.getSetting("ModelsInvocationPath") ); - } - - // Incoming raw DSL or use locally? - if ( structKeyExists(arguments,"rawDSL") ){ - wireBoxDSL = arguments.rawDSL; - } - - // Register LogBox Configuration - if( structKeyExists( wireBoxDSL, "logBoxConfig") ){ - logBoxConfig(wireBoxDSL.logBoxConfig); - } - - // Register Parent Injector - if( structKeyExists( wireBoxDSL, "parentInjector") ){ - parentInjector( wireBoxDSL.parentInjector ); - } - - // Register Server Scope Registration - if( structKeyExists( wireBoxDSL, "scopeRegistration") ){ - scopeRegistration(argumentCollection=wireBoxDSL.scopeRegistration); - } - - // Register CacheBox - if( structKeyExists( wireBoxDSL, "cacheBox") ){ - cacheBox(argumentCollection=wireBoxDSL.cacheBox); - } - - // Register metadataCache - if( structKeyExists( wireBoxDSL, "metadataCache") ){ - setMetadataCache( wireBoxDSL.metadataCache ); - } - - // Register Custom DSL - if( structKeyExists( wireBoxDSL, "customDSL") ){ - structAppend(instance.customDSL, wireBoxDSL.customDSL, true); - } - - // Register Custom Scopes - if( structKeyExists( wireBoxDSL, "customScopes") ){ - structAppend(instance.customScopes, wireBoxDSL.customScopes, true); + } + + // generate the mapping for the first name passed + variables.mappings[ name ] = new coldbox.system.ioc.config.Mapping( name ); + + // set as the current mapping + arrayAppend( variables.currentMapping, variables.mappings[ name ] ); + + // Set aliases, scopes and types + variables.mappings[ name ].setAlias( arguments.alias ).setType( this.TYPES.CFC ); + + // Loop and create alias references + for ( var x = 2; x lte arrayLen( arguments.alias ); x++ ) { + variables.mappings[ arguments.alias[ x ] ] = variables.mappings[ name ]; + } + + return this; + } + + /** + * Map to a destination CFC class path. + * + * @path The class path to the object to map + */ + Binder function to( required path ){ + for ( var mapping in variables.currentMapping ) { + mapping.setPath( arguments.path ).setType( this.TYPES.CFC ); + } + return this; + } + + /** + * this method lets you use an abstract or parent mapping as a template for other like objects + * + * @alias The parent class to copy dependencies and definitions from + */ + Binder function parent( required alias ){ + // copy parent class's memento instance, exclude alias, name and path + for ( var mapping in variables.currentMapping ) { + mapping.processMemento( getMapping( arguments.alias ).getMemento(), "alias,name" ); + } + return this; + } + + /** + * Map an alias to a factory and its executing method + * + * @factory The mapping factory reference name + * @method The method to execute + */ + Binder function toFactoryMethod( required factory, required method ){ + for ( var mapping in variables.currentMapping ) { + mapping + .setType( this.TYPES.FACTORY ) + .setPath( arguments.factory ) + .setMethod( arguments.method ); + } + return this; + } + + /** + * Map a method argument to a factory method + * + * @name The name of the method argument (Not used for: JAVA,WEBSERVICE) + * @ref The reference mapping id this method argument maps to + * @dsl The construction dsl this argument references. If used, the name value must be used. + * @value The explicit value of the method argument, if passed. + * @javaCast The type of javaCast() to use on the value of the argument. Only used if using dsl or ref arguments + * @required If the argument is required or not, by default we assume required DI arguments + * @type The type of the argument + */ + Binder function methodArg( + name, + ref, + dsl, + value, + javaCast, + required required=true, + type = "any" + ){ + for ( var mapping in variables.currentMapping ) { + mapping.addDIMethodArgument( argumentCollection = arguments ); + } + return this; + } + + /** + * Map to a java destination class path + * + * @path The class path to the object to map + */ + Binder function toJava( required path ){ + for ( var mapping in variables.currentMapping ) { + mapping.setPath( arguments.path ).setType( this.TYPES.JAVA ); + } + return this; + } + + /** + * Map to a webservice destination class path + * + * @path The webservice path to the object to map + */ + Binder function toWebservice( required path ){ + for ( var mapping in variables.currentMapping ) { + mapping.setPath( arguments.path ).setType( this.TYPES.WEBSERVICE ); + } + return this; + } + + /** + * Map to an rss destination + * + * @path The rss path to the object to map + */ + Binder function toRSS( required path ){ + for ( var mapping in variables.currentMapping ) { + mapping.setPath( arguments.path ).setType( this.TYPES.RSS ); + } + return this; + } + + /** + * Map to a dsl that will be used to create the mapped object + * + * @dsl The dsl to the object to map + */ + Binder function toDSL( required dsl ){ + for ( var mapping in variables.currentMapping ) { + mapping.setDSL( arguments.dsl ).setType( this.TYPES.DSL ); + } + return this; + } + + /** + * Map to a provider object that must implement coldbox.system.ioc.IProvider or a closure or UDF + * + * @provider The provider to map to + */ + Binder function toProvider( required provider ){ + for ( var mapping in variables.currentMapping ) { + mapping.setPath( arguments.provider ).setType( this.TYPES.PROVIDER ); + } + return this; + } + + /** + * Map to a constant value + * + * @value The value to bind to + */ + Binder function toValue( required value ){ + for ( var mapping in variables.currentMapping ) { + mapping.setValue( arguments.value ).setType( this.TYPES.CONSTANT ); + } + return this; + } + + /** + * You can choose what method will be treated as the constructor. By default the value is 'init', so don't call this method if that is the case + * + * @constructor The constructor method to use for the mapped object + */ + Binder function constructor( required constructor ){ + for ( var mapping in variables.currentMapping ) { + mapping.setConstructor( arguments.constructor ); + } + return this; + } + + /** + * Positional or named value arguments to use when initializing the mapping. (CFC-only) + */ + Binder function initWith(){ + for ( var thisArg in arguments ) { + for ( var mapping in variables.currentMapping ) { + mapping.addDIConstructorArgument( name = thisArg, value = arguments[ thisArg ] ); } + } + return this; + } + + /** + * If you call this method on an object mapping, the object's constructor will not be called. By default all constructors are called + */ + Binder function noInit(){ + for ( var mapping in variables.currentMapping ) { + mapping.setAutoInit( false ); + } + return this; + } + + /** + * Tells WireBox to do a virtual inheritance mixin of the target and this passed mapping + * + * @mapping The mapping name of CFC to create the virtual inheritance from + */ + Binder function virtualInheritance( required mapping ){ + for ( var thisMapping in variables.currentMapping ) { + thisMapping.setVirtualInheritance( arguments.mapping ); + } + return this; + } + + /** + * If this method is called, the mapped object will be created once the injector starts up. Basically, not lazy loaded + */ + Binder function asEagerInit(){ + for ( var mapping in variables.currentMapping ) { + mapping.setEagerInit( true ); + } + return this; + } + + /** + * If you call this method on an object mapping, the object will NOT be inspected for injection/wiring metadata, it will use ONLY whatever you define in the mapping + */ + Binder function noAutowire(){ + for ( var mapping in variables.currentMapping ) { + mapping.setAutowire( false ); + } + return this; + } + + /** + * Used to set the current working mapping name in place for the maping DSL. An exception is thrown if the mapping does not exist yet. + * + * @alias The name of the mapping to set as the current working mapping + * + * @throws InvalidMappingStateException - If the alias has not been registered yet + */ + Binder function with( required alias ){ + // Check if it has been registered yet + if ( mappingExists( arguments.alias ) ) { + variables.currentMapping = [ variables.mappings[ arguments.alias ] ]; + return this; + } + throw( + message = "The mapping '#arguments.alias#' has not been registered yet", + type = "InvalidMappingStateException" + ); + } + + /** + * Map a constructor argument to a mapping + * + * @name The name of the constructor argument (Not used for: JAVA,WEBSERVICE) + * @ref The reference mapping id this constructor argument maps to + * @dsl The construction dsl this argument references. If used, the name value must be used. + * @value The explicit value of the constructor argument, if passed. + * @javaCast The type of javaCast() to use on the value of the argument. Only used if using dsl or ref arguments + * @required If the argument is required or not, by default we assume required DI arguments + * @type The type of the argument + */ + Binder function initArg( + name, + ref, + dsl = "", + value, + javaCast, + required required=true, + type = "any" + ){ + for ( var mapping in variables.currentMapping ) { + mapping.addDIConstructorArgument( argumentCollection = arguments ); + } + return this; + } + + /** + * Map setter injection + * + * @name The name of the setter to inject + * @ref The reference mapping id this setter maps to + * @dsl The construction dsl this setter references. If used, the name value must be used. + * @value The explicit value of the setter, if passed. + * @javaCast The type of javaCast() to use on the value of the value. Only used if using dsl or ref arguments + * @argName The name of the argument to use, if not passed, we default it to the setter name + */ + Binder function setter( + required name, + ref, + dsl, + value, + javaCast, + argName + ){ + for ( var mapping in variables.currentMapping ) { + mapping.addDISetter( argumentCollection = arguments ); + } + return this; + } + + /** + * Map property injection + * + * @name The name of the property to inject + * @ref The reference mapping id this property maps to + * @dsl The construction dsl this property references. If used, the name value must be used. + * @value The explicit value of the property, if passed. + * @javaCast The type of javaCast() to use on the value of the value. Only used if using dsl or ref arguments + * @scope The scope in the CFC to inject the property to. By default it will inject it to the variables scope + * @required If the property is required or not, by default we assume required DI + * @type The type of the property + */ + Binder function property( + required name, + ref, + dsl, + value, + javaCast, + scope = "variables", + required required=true, + type = "any" + ){ + for ( var mapping in variables.currentMapping ) { + mapping.addDIProperty( argumentCollection = arguments ); + } + return this; + } + + /** + * The methods to execute once DI completes on the mapping + * + * @methods A list or an array of methods to execute once the mapping is created, inited and DI has happened. + */ + Binder function onDIComplete( required methods ){ + // inflate list + if ( isSimpleValue( arguments.methods ) ) { + arguments.methods = listToArray( arguments.methods ); + } + // store list + for ( var mapping in variables.currentMapping ) { + mapping.setOnDIComplete( arguments.methods ); + } + return this; + } + + /** + * Add a new provider method mapping + * + * @method The provided method to override or inject as a provider + * @mapping The mapping to provide via the selected method + */ + Binder function providerMethod( required method, required mapping ){ + for ( var thisMapping in variables.currentMapping ) { + thisMapping.addProviderMethod( argumentCollection = arguments ); + } + return this; + } + + /** + * Map an object into a specific persistence scope + * + * @scope The scope to map to, use a valid WireBox Scope by using binder.SCOPES.* or a custom scope + * + * @throws InvalidScopeMapping - Trying to register into an invalid scope + */ + Binder function into( required scope ){ + // check if invalid scope + if ( + NOT this.SCOPES.isValidScope( arguments.scope ) AND NOT structKeyExists( + variables.customScopes, + arguments.scope + ) + ) { + throw( + message = "Invalid WireBox Scope: '#arguments.scope#'", + detail = "Please make sure you are using a valid scope, valid scopes are: #arrayToList( this.SCOPES.getValidScopes() )# AND custom scopes: #structKeyList( variables.customScopes )#", + type = "Binder.InvalidScopeMapping" + ); + } + for ( var mapping in variables.currentMapping ) { + mapping.setScope( arguments.scope ); + } + return this; + } + + /** + * Map as a singleton, shortcut to using 'in( this.SCOPES.SINGLETON )' + */ + Binder function asSingleton(){ + return this.into( this.SCOPES.SINGLETON ); + } + + /** + * Tells persistence scopes to build, wire, and do onDIComplete() on objects in an isolated lock. This will disallow circular references unless object providers are used. By default all object's constructors are the only thread safe areas + */ + Binder function threadSafe(){ + for ( var mapping in variables.currentMapping ) { + mapping.setThreadSafe( true ); + } + return this; + } + + /** + * This is the default wiring of objects that allow circular dependencies. By default all object's constructors are the only thread safe areas + */ + Binder function notThreadSafe(){ + for ( var mapping in variables.currentMapping ) { + mapping.setThreadSafe( false ); + } + return this; + } + + /** + * This is a closure that will be able to influence the creation of the instance + * + * @influenceClosure The closure to use for influencing constructions + */ + Binder function withInfluence( required influenceClosure ){ + for ( var mapping in variables.currentMapping ) { + mapping.setInfluenceClosure( arguments.influenceClosure ); + } + return this; + } + + /** + * Adds a structure of metadata to be stored with the mapping for later retrieval by the developer in events, manually or builders + * + * @data The data structure to store with the mapping + */ + Binder function extraAttributes( required struct data ){ + for ( var mapping in variables.currentMapping ) { + mapping.setExtraAttributes( arguments.data ); + } + return this; + } + + /** + * Adds one, a list or an array of UDF templates to mixin to a CFC + * + * @mixins The udf include location(s) to mixin at runtime + */ + Binder function mixins( required mixins ){ + if ( isSimpleValue( arguments.mixins ) ) { + arguments.mixins = listToArray( arguments.mixins ); + } + for ( var mapping in variables.currentMapping ) { + mapping.setMixins( arguments.mixins ); + } + return this; + } + + /** + * Link a parent injector to this configuration binder + * + * @injector A parent injector to link + */ + Binder function parentInjector( required injector ){ + variables.parentInjector = arguments.injector; + return this; + } + + /** + * Configure the stop recursion classes + * + * @classes A list or array of classes to use so the injector can stop when looking for dependencies in inheritance chains + */ + Binder function stopRecursions( required classes ){ + // inflate incoming locations + if ( isSimpleValue( arguments.classes ) ) { + arguments.classes = listToArray( arguments.classes ); + } + // Save them + variables.stopRecursions = arguments.classes; + + return this; + } + + /** + * Use to define injector scope registration + * + * @enabled Enable registration or not (defaults=false) Boolean + * @scope The scope to register on, defaults to application scope + * @key The key to use in the scope, defaults to wireBox + */ + Binder function scopeRegistration( + boolean enabled = variables.DEFAULTS.scopeRegistration.enabled, + scope = variables.DEFAULTS.scopeRegistration.scope, + key = variables.DEFAULTS.scopeRegistration.key + ){ + structAppend( variables.scopeRegistration, arguments, true ); + return this; + } + + /** + * Register one or more package scan locations for CFC lookups + * + * @locations A list or array of locations to add to package scanning.e.g.: ['coldbox','com.myapp','transfer'] + */ + Binder function scanLocations( required locations ){ + // inflate incoming locations + if ( isSimpleValue( arguments.locations ) ) { + arguments.locations = listToArray( arguments.locations ); + } + // Process locations + arguments.locations + .filter( function( thisLocation ){ + return ( + !structKeyExists( variables.scanLocations, arguments.thisLocation ) + AND + len( arguments.thisLocation ) + ); + } ) + .each( function( thisLocation ){ + // Process creation path & Absolute Path + variables.scanLocations[ thisLocation ] = expandPath( + "/" & replace( thisLocation, ".", "/", "all" ) & "/" + ); + } ); - // Append Register Scan Locations - if( structKeyExists( wireBoxDSL, "scanLocations") ){ - scanLocations( wireBoxDSL.scanLocations ); + return this; + } + + /** + * Try to remove all the scan locations passed in + * + * @locations Locations to remove from the lookup. A list or array of locations + */ + function removeScanLocations( required locations ){ + // inflate incoming locations + if ( isSimpleValue( arguments.locations ) ) { + arguments.locations = listToArray( arguments.locations ); + } + + // Loop and remove + arguments.locations.each( function( thisLocation ){ + structDelete( variables.scanLocations, thisLocation ); + } ); + } + + /** + * Configurate CacheBox operations + * + * @configFile The configuration file to use for loading CacheBox if creating it + * @cacheFactory The CacheBox cache factory instance to link WireBox to + * @enabled Enable or Disable CacheBox Integration, if you call this method then enabled is set to true as most likely you are trying to enable it + * @classNamespace The package namespace to use for creating or connecting to CacheBox. Defaults to: coldbox.system.cache + */ + Binder function cachebox( + configFile = "", + cacheFactory = "", + boolean enabled = true, + classNamespace = variables.DEFAULTS.cachebox.classNamespace + ){ + structAppend( variables.cacheBox, arguments, true ); + return this; + } + + /** + * Alias to get cachebox configuration + * + * @deprecated Remove by v7: use getCacheBox() instead + */ + struct function getCacheBoxConfig(){ + return variables.cachebox; + } + + /** + * Map an object into CacheBox + * + * @key You can override the key it will use for storing in cache. By default it uses the name of the mapping + * @timeout Object Timeout, else defaults to whatever the default is in the choosen cache + * @lastAccessTimeout Object Timeout, else defaults to whatever the default is in the choosen cache + * @provider Uses the 'default' cache provider by default + */ + Binder function inCacheBox( + key = "", + timeout = "", + lastAccessTimeout = "", + provider = "default" + ){ + for ( var mapping in variables.currentMapping ) { + // if key not passed, build a mapping name + if ( NOT len( arguments.key ) ) { + if ( len( mapping.getPath() ) ) { + arguments.key = "wirebox-#mapping.getPath()#"; + } else { + arguments.key = "wirebox-#mapping.getName()#"; + } } - - // Append Register Stop Recursions - if( structKeyExists( wireBoxDSL, "stopRecursions") ){ - stopRecursions( wireBoxDSL.stopRecursions ); + // store the mapping info. + mapping.setScope( this.SCOPES.CACHEBOX ).setCacheProperties( argumentCollection = arguments ); + } + + return this; + } + + /** + * Register a new custom dsl namespace + * + * @namespace The namespace you would like to register + * @path The instantiation path to the CFC that implements this scope, it must have an init() method and implement: coldbox.system.ioc.dsl.IDSLBuilder + */ + Binder function mapDSL( required namespace, required path ){ + variables.customDSL[ arguments.namespace ] = arguments.path; + return this; + } + + /** + * Register a new WireBox custom scope + * + * @annotation The unique scope name to register. This translates to an annotation value on CFCs + * @path The path to the CFC that implements this scope, it must have an init() method and implement: coldbox.system.ioc.scopes.IScope + */ + Binder function mapScope( required annotation, required path ){ + variables.customScopes[ arguments.annotation ] = arguments.path; + return this; + } + + /** + * Set the logBox Configuration to use + * + * @config The logbox configuration struct + */ + Binder function logBoxConfig( required config ){ + variables.logBoxConfig = arguments.config; + return this; + } + + /** + * Load a data configuration CFCs data DSL into this configuration + * + * @rawDSL The data configuration DSL structure to load, else look internally + */ + Binder function loadDataDSL( struct rawDSL ){ + var wireBoxDSL = variables.wirebox; + + // Coldbox Context Attached + if ( isObject( variables.coldbox ) ) { + // create scan location for model convention as the first one. + this.scanLocations( variables.coldbox.getSetting( "ModelsInvocationPath" ) ); + } + + // Incoming raw DSL or use locally? + if ( !isNull( arguments.rawDSL ) ) { + wireBoxDSL = arguments.rawDSL; + } + + // Register LogBox Configuration + if ( structKeyExists( wireBoxDSL, "logBoxConfig" ) ) { + variables.logBoxConfig = wireBoxDSL.logBoxConfig; + } + + // Register Parent Injector + if ( structKeyExists( wireBoxDSL, "parentInjector" ) ) { + variables.parentInjector = wireBoxDSL.parentInjector; + } + + // Register Server Scope Registration + if ( structKeyExists( wireBoxDSL, "scopeRegistration" ) ) { + this.scopeRegistration( argumentCollection = wireBoxDSL.scopeRegistration ); + } + + // Register CacheBox + if ( structKeyExists( wireBoxDSL, "cacheBox" ) ) { + this.cacheBox( argumentCollection = wireBoxDSL.cacheBox ); + } + + // Register metadataCache + if ( structKeyExists( wireBoxDSL, "metadataCache" ) ) { + variables.metadataCache = wireBoxDSL.metadataCache; + } + + // Register Custom DSL + if ( structKeyExists( wireBoxDSL, "customDSL" ) ) { + structAppend( + variables.customDSL, + wireBoxDSL.customDSL, + true + ); + } + + // Register Custom Scopes + if ( structKeyExists( wireBoxDSL, "customScopes" ) ) { + structAppend( + variables.customScopes, + wireBoxDSL.customScopes, + true + ); + } + + // Append Register Scan Locations + if ( structKeyExists( wireBoxDSL, "scanLocations" ) ) { + this.scanLocations( wireBoxDSL.scanLocations ); + } + + // Append Register Stop Recursions + if ( structKeyExists( wireBoxDSL, "stopRecursions" ) ) { + this.stopRecursions( wireBoxDSL.stopRecursions ); + } + + // Register listeners + if ( structKeyExists( wireBoxDSL, "listeners" ) ) { + for ( var thisListener in wireboxDSL.listeners ) { + this.listener( argumentCollection = thisListener ); } - - // Register listeners - if( structKeyExists( wireBoxDSL, "listeners") ){ - for(key=1; key lte arrayLen(wireBoxDSL.listeners); key++ ){ - listener(argumentCollection=wireBoxDSL.listeners[key]); - } + } + + // Register Mappings + if ( structKeyExists( wireBoxDSL, "mappings" ) ) { + // iterate and register + for ( var key in wireboxDSL.mappings ) { + // create mapping & process its data memento + map( key ); + variables.mappings[ key ].processMemento( wireBoxDSL.mappings[ key ] ); } + } + + return this; + } + + /** + * Get the mapping's memento structure + */ + struct function getMemento(){ + return variables.filter( function( k, v ){ + return ( !isCustomFunction( v ) ); + } ); + } + + /** + * Discover all eager inits in the Binder and build them. + */ + Binder function processEagerInits(){ + variables.mappings + .filter( function( key, thisMapping ){ + return ( arguments.thisMapping.isEagerInit() ); + } ) + .each( function( key, thisMapping ){ + variables.injector.getInstance( arguments.thisMapping.getName() ); + } ); - // Register Mappings - if( structKeyExists( wireBoxDSL, "mappings") ){ - // iterate and register - for(key in wireboxDSL.mappings){ - // create mapping & process its data memento - map(key); - instance.mappings[ key ].processMemento( wireBoxDSL.mappings[key] ); - } - } - - - - - - - - - - - - - - - - - - - - - - var mappingError = ""; - instance.mappings.filter( function( key, thisMapping ){ - return ( !thisMapping.isDiscovered() ); - } ).each( function( key, thisMapping ){ + return this; + } + + /** + * Process all registered mappings, called by injector when ready to start serving requests + * Processing means that we will iterate over all NON discovered mappings and call each + * mapping's `process()` method so all metadata can be read and registered. + */ + Binder function processMappings(){ + var mappingError = ""; + + variables.mappings + .filter( function( key, thisMapping ){ + return ( !arguments.thisMapping.isDiscovered() ); + } ) + .each( function( key, thisMapping ){ try { - // process the metadata - thisMapping.process( binder=this, injector=instance.injector ); - // is it eager? - if( thisMapping.isEagerInit() ){ - instance.injector.getInstance( thisMapping.getName() ); - } - } catch( any e ) { + // process the metadata + arguments.thisMapping.process( binder = this, injector = variables.injector ); + } catch ( any e ) { // Remove bad mapping - instance.mappings.delete( key ); + variables.mappings.delete( key ); mappingError = e; } - } ); - if( !isSimpleValue( mappingError ) ) { - throw( object=mappingError ); - } - - - - - - - - - - - - - - // Name check? - if( NOT len(arguments.name) ){ - arguments.name = listLast(arguments.class,"."); - } - // add listener - arrayAppend(instance.listeners, arguments); - - if ( arguments.register ) { - getInjector().registerListener( arguments ); - } - - return this; - - - - - - - - - - - - - - - - // map eagerly - map(arguments.aspect).asEagerInit().asSingleton(); - - // register the aspect - for( var mapping in getCurrentMapping() ) { - mapping.setAspect( true ).setAspectAutoBinding( arguments.autoBinding ); - } - - return this; - - - - - - - return createObject("component","coldbox.system.aop.Matcher").init(); - - - - - - - - - - // cleanup aspect - if( isSimpleValue(arguments.aspects) ){ arguments.aspects = listToArray(arguments.aspects); } - // register it - arrayAppend(instance.aspectBindings, arguments); - - return this; - - - - - - - - - - + // Verify exceptions + if ( !isSimpleValue( mappingError ) ) { + throw( object = mappingError ); + } + + return this; + } + + /** + * Add a new listener configuration + * + * @class The class of the listener + * @properties The structure of properties for the listner + * @name The name of the listener + * @register If true, registers the listener right away + */ + Binder function listener( + required class, + struct properties = {}, + name = "", + boolean register = false + ){ + // Name check? + if ( NOT len( arguments.name ) ) { + arguments.name = listLast( arguments.class, "." ); + } + + // add listener + arrayAppend( variables.listeners, arguments ); + + if ( arguments.register ) { + getInjector().registerListener( arguments ); + } + + return this; + } + + /** + * -------------------------------------------------- + * AOP Mapping Methods + * -------------------------------------------------- + */ + + /** + * Map a new aspect + * + * @aspect The name or aliases of the aspect + * @autoBinding Allow autobinding of this aspect or not? Defaults to true + */ + Binder function mapAspect( required aspect, boolean autoBinding = true ){ + // map the aspect + map( arguments.aspect ).asEagerInit().asSingleton(); + + // register the aspect + for ( var mapping in variables.currentMapping ) { + mapping.setAspect( true ).setAspectAutoBinding( arguments.autoBinding ); + } + + return this; + } + + /** + * Create a new matcher class for usage in class or method matching + * + * @return coldbox.system.aop.Matcher + */ + function match(){ + return new coldbox.system.aop.Matcher(); + } + + /** + * Bind a aspects to classes and methods + * + * @classes The class matcher that will be affected with this aspect binding + * @methods The method matcher that will be affected with this aspect binding + * @aspects The name or list of names or array of names of aspects to apply to the classes and method matchers + */ + Binder function bindAspect( + required classes, + required methods, + required aspects + ){ + // cleanup aspect + if ( isSimpleValue( arguments.aspects ) ) { + arguments.aspects = listToArray( arguments.aspects ); + } + // register it + arrayAppend( variables.aspectBindings, arguments ); + + return this; + } + +} diff --git a/system/ioc/config/DefaultBinder.cfc b/system/ioc/config/DefaultBinder.cfc index 071ae49a5..fbb0b2614 100644 --- a/system/ioc/config/DefaultBinder.cfc +++ b/system/ioc/config/DefaultBinder.cfc @@ -6,17 +6,17 @@ * WireBox injector is created **/ component extends="coldbox.system.ioc.config.Binder"{ - + /** * Configure WireBox, that's it! */ function configure(){ - + // The WireBox configuration structure DSL wireBox = { // Default LogBox Configuration file logBoxConfig = "coldbox.system.ioc.config.LogBox", - + // CacheBox Integration OFF by default cacheBox = { enabled = false @@ -24,38 +24,38 @@ component extends="coldbox.system.ioc.config.Binder"{ // cacheFactory = "" A reference to an already instantiated CacheBox CacheFactory // classNamespace = "" A class path namespace to use to create CacheBox: Default=coldbox.system.cache or wirebox.system.cache }, - + // Name of a CacheBox cache to store metadata in to speed up start time. // Since metadata is already stored in memory, this is only useful for a disk, etc cache that persists across restarts. metadataCache='', - + // Scope registration, automatically register a wirebox injector instance on any CF scope // By default it registeres itself on application scope scopeRegistration = { - enabled = true, - scope = "application", // server, cluster, session, application - key = "wireBox" + enabled= true, + scope = "application", // server, cluster, session, application + key = "wireBox" }, // DSL Namespace registrations customDSL = { // namespace = "mapping name" }, - + // Custom Storage Scopes customScopes = { // annotationName = "mapping name" }, - + // Package scan locations scanLocations = [], - + // Stop Recursions stopRecursions = [], - + // Parent Injector to assign to the configured injector, this must be an object reference parentInjector = "", - + // Register all event listeners here, they are created in the specified order listeners = [ // { class="", name="", properties={} } diff --git a/system/ioc/config/LogBox.cfc b/system/ioc/config/LogBox.cfc index d02edd9a3..97e527f9c 100644 --- a/system/ioc/config/LogBox.cfc +++ b/system/ioc/config/LogBox.cfc @@ -1,11 +1,11 @@ /** -* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp -* www.ortussolutions.com -* --- -* The logging configuration object for WireBox Standalone version. -* You can make changes here to determine how WireBox logs information. For more -* information about logBox visit: https://logbox.ortusbooks.com -**/ + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * The logging configuration object for WireBox Standalone version. + * You can make changes here to determine how WireBox logs information. For more + * information about logBox visit: https://logbox.ortusbooks.com + */ component{ /** @@ -16,17 +16,11 @@ component{ // Define Appenders appenders = { console = { - class="ConsoleAppender" + class = "ConsoleAppender" } - /**, - cflogs = { - class="coldbox.system.logging.appenders.CFAppender", - properties = { fileName="ColdBox-WireBox"} - } - **/ }, // Root Logger - root = { levelmax="INFO", appenders="*" } + root = { levelmax = "INFO", appenders = "*" } }; } diff --git a/system/ioc/config/Mapping.cfc b/system/ioc/config/Mapping.cfc index 5c484d098..4411d2cf1 100644 --- a/system/ioc/config/Mapping.cfc +++ b/system/ioc/config/Mapping.cfc @@ -1,947 +1,931 @@ - - - - - - - - - - - // Configure Instance - instance = { - // Setup the mapping name - name = arguments.name, - // Setup the alias list for this mapping. - alias = [], - // Mapping Type - type = "", - // Mapping Value (If Any) - value = "", - // Mapped instantiation path or mapping - path = "", - // A factory method to execute on the mapping if this is a factory mapping - method = "", - // Mapped constructor - constructor = "init", - // Discovery and wiring flag - autoWire = "", - // Auto init or not - autoInit = true, - // Lazy load the mapping or not - eagerInit = "", - // The storage or visibility scope of the mapping - scope = "", - // A construction dsl - dsl = "", - // Caching parameters - cache = {provider="", key="", timeout="", lastAccessTimeout=""}, - // Explicit Constructor arguments - DIConstructorArgs = [], - // Explicit Properties - DIProperties = [], - // Explicit Setters - DISetters = [], - // Explicit method arguments - DIMethodArgs = [], - // Post Processors - onDIComplete = [], - // Flag used to distinguish between discovered and non-discovered mappings - discovered = false, - // original object's metadata - metadata = {}, - // discovered provider methods - providerMethods = [], - // AOP aspect - aspect = false, - // AutoAspectBinding - autoAspectBinding = true, - // Virtual Inhertiance - virtualInheritance = "", - // Extra Attributes - extraAttributes = {}, - // Mixins - mixins = [], - // Thread safety on wiring - threadSafe = "", - // A closure that can influence the creation of the instance - influenceClosure = "" - }; - - // DI definition structure - DIDefinition = { name="", value=JavaCast( "null", "" ), dsl=JavaCast( "null", "" ), scope="variables", javaCast=JavaCast( "null", "" ), ref=JavaCast( "null", "" ), required=false, argName="", type="any" }; - - return this; - - - - - - - - - - - - - - var x = 1; - - - // if excludes is passed as an array, convert to list - if( isArray( arguments.excludes ) ){ - arguments.excludes = arrayToList( arguments.excludes ); +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * I model a WireBox object mapping in all of its glory and splendour to create the + * object it represents + */ +component accessors="true"{ + + /** + * Mapping Properties + */ + + property name="name"; + property name="alias" type="array"; + property name="type"; + property name="value"; + property name="path"; + property name="method"; + property name="constructor"; + property name="autoWire"; + property name="autoInit" type="boolean"; + property name="eagerInit"; + property name="scope"; + property name="dsl"; + property name="cache" type="struct"; + property name="DIConstructorArguments"; + property name="DIProperties" type="array"; + property name="DISetters" type="array"; + property name="DIMethodArguments" type="array"; + property name="onDIComplete" type="array"; + property name="discovered" type="boolean"; + property name="objectMetadata" type="struct"; + property name="providerMethods" type="array"; + property name="aspect" type="boolean"; + property name="aspectAutoBinding" type="boolean"; + property name="virtualInheritance"; + property name="extraAttributes" type="struct"; + property name="mixins" type="array"; + property name="threadSafe"; + property name="influenceClosure"; + + /** + * Constructor + * + * @name The mapping name + */ + function init( required name ){ + // Setup the mapping name + variables.name = arguments.name; + // Setup the alias list for this mapping. + variables.alias = []; + // Mapping Type + variables.type = ""; + // Mapping Value (If Any) + variables.value = ""; + // Mapped instantiation path or mapping + variables.path = ""; + // A factory method to execute on the mapping if this is a factory mapping + variables.method = ""; + // Mapped constructor + variables.constructor = "init"; + // Discovery and wiring flag + variables.autoWire = ""; + // Auto init or not + variables.autoInit = true; + // Lazy load the mapping or not + variables.eagerInit = ""; + // The storage or visibility scope of the mapping + variables.scope = ""; + // A construction dsl + variables.dsl = ""; + // Caching parameters + variables.cache = { + provider : "", + key : "", + timeout : "", + lastAccessTimeout : "" + }; + // Explicit Constructor arguments + variables.DIConstructorArguments = []; + // Explicit Properties + variables.DIProperties = []; + // Explicit Setters + variables.DISetters = []; + // Explicit method arguments + variables.DIMethodArguments = []; + // Post Processors + variables.onDIComplete = []; + // Flag used to distinguish between discovered and non-discovered mappings + variables.discovered = false; + // original object's metadata + variables.objectMetadata = {}; + // discovered provider methods + variables.providerMethods = []; + // AOP aspect + variables.aspect = false; + // aspectAutoBinding + variables.aspectAutoBinding = true; + // Virtual Inhertiance + variables.virtualInheritance = ""; + // Extra Attributes + variables.extraAttributes = {}; + // Mixins + variables.mixins = []; + // Thread safety on wiring + variables.threadSafe = ""; + // A closure that can influence the creation of the mapping + variables.influenceClosure = ""; + + return this; + } + + /** + * Get the mapping's memento structure + */ + struct function getMemento(){ + return variables.filter( function( k, v ){ + return ( !isCustomFunction( v ) ); + } ); + } + + /** + * Process a mapping memento. Basically takes in a struct of data to process the mapping's data with. + * + * @memento The data memento to process + * @excludes List of memento keys to not process + */ + Mapping function processMemento( required memento, excludes="" ){ + // if excludes is passed as an array, convert to list + if ( isArray( arguments.excludes ) ) { + arguments.excludes = arrayToList( arguments.excludes ); + } + + // append incoming memento data + for ( var key in arguments.memento ) { + // if current key is in excludes list, skip and continue to next loop + if ( listFindNoCase( arguments.excludes, key ) ) { + continue; } - // append incoming memento data - for( var key in arguments.memento ){ - - // if current key is in excludes list, skip and continue to next loop - if( listFindNoCase( arguments.excludes, key ) ){ - continue; + switch ( key ) { + // process cache properties + case "cache": { + setCacheProperties( argumentCollection = arguments.memento.cache ); + break; } - switch( key ){ - - // process cache properties - case "cache" : { - setCacheProperties( argumentCollection=arguments.memento.cache ); - break; - } - - // process constructor args - case "DIConstructorArgs" : { - for( x=1; x lte arrayLen( arguments.memento.DIConstructorArgs ); x++ ){ - addDIConstructorArgument( argumentCollection=arguments.memento.DIConstructorArgs[ x ] ); - } - break; - } - - // process properties - case "DIProperties" : { - for( x=1; x lte arrayLen( arguments.memento.DIProperties ); x++){ - addDIProperty( argumentCollection=arguments.memento.DIProperties[ x ] ); - } - break; + // process constructor args + case "DIConstructorArguments": { + for ( var x = 1; x lte arrayLen( arguments.memento.DIConstructorArguments ); x++ ) { + addDIConstructorArgument( argumentCollection = arguments.memento.DIConstructorArguments[ x ] ); } + break; + } - // process DISetters - case "DISetters" : { - for( x=1; x lte arrayLen( arguments.memento.DISetters ); x++){ - addDISetter( argumentCollection=arguments.memento.DISetters[ x ] ); - } - break; + // process properties + case "DIProperties": { + for ( var x = 1; x lte arrayLen( arguments.memento.DIProperties ); x++ ) { + addDIProperty( argumentCollection = arguments.memento.DIProperties[ x ] ); } + break; + } - // process DIMethodArgs - case "DIMethodArgs" : { - for( x=1; x lte arrayLen( arguments.memento.DIMethodArgs ); x++){ - addDIMethodArgument( argumentCollection=arguments.memento.DIMethodArgs[ x ] ); - } - break; + // process DISetters + case "DISetters": { + for ( var x = 1; x lte arrayLen( arguments.memento.DISetters ); x++ ) { + addDISetter( argumentCollection = arguments.memento.DISetters[ x ] ); } + break; + } - // process path - case "path" : { - // Only override if it doesn't exist or empty - if( !instance.keyExists( "path" ) OR !len( instance.path ) ){ - instance[ "path" ] = arguments.memento[ "path" ]; - } - break; + // process DIMethodArguments + case "DIMethodArguments": { + for ( var x = 1; x lte arrayLen( arguments.memento.DIMethodArguments ); x++ ) { + addDIMethodArgument( argumentCollection = arguments.memento.DIMethodArguments[ x ] ); } + break; + } - default:{ - instance[ key ] = arguments.memento[ key ]; - break; + // process path + case "path": { + // Only override if it doesn't exist or empty + if ( !len( variables.path ) ) { + variables.path = arguments.memento[ "path" ]; } - }// end switch - - } + break; + } - return this; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - structAppend( instance.cache, arguments, true); - return this; - - - - - - - - - - - - - - - - - - - - - - var def = getDIDefinition(); - var x = 1; - // check if already registered, if it is, just return - for(x=1; x lte arrayLen(instance.DIConstructorArgs); x++){ - if( structKeyExists( arguments, "name" ) AND structKeyExists( instance.DIConstructorArgs[ x ], "name" ) AND - instance.DIConstructorArgs[ x ].name eq arguments.name ){ return this;} + default: { + variables[ key ] = arguments.memento[ key ]; + break; + } } - // Register new constructor argument. - structAppend(def, arguments, true); - arrayAppend( instance.DIConstructorArgs, def ); - - return this; - - - - - - - - - - - - - - var def = getDIDefinition(); - var x = 1; - // check if already registered, if it is, just return - for(x=1; x lte arrayLen(instance.DIMethodArgs); x++){ - if( structKeyExists(instance.DIMethodArgs[ x ],"name") AND - instance.DIMethodArgs[ x ].name eq arguments.name ){ return this;} + // end switch + } + + return this; + } + + /** + * Checks if the mapping needs virtual inheritace or not + */ + boolean function isVirtualInheritance(){ + return len( variables.virtualInheritance ) GT 0; + } + + /** + * Flag describing if you are using autowire or not as Boolean + */ + boolean function isAutoWire(){ + return ( isBoolean( variables.autowire ) ? variables.autowire : false ); + } + + /** + * Flag describing if this mapping is an AOP aspect or not + */ + boolean function isAspect(){ + return ( isBoolean( variables.aspect ) ? variables.aspect : false ); + } + + /** + * Is this mapping an auto aspect binding + */ + boolean function isAspectAutoBinding(){ + return ( isBoolean( variables.aspectAutoBinding ) ? variables.aspectAutoBinding : false ); + } + + /** + * Using auto init or not + */ + boolean function isAutoInit(){ + return ( isBoolean( variables.autoInit ) ? variables.autoInit : false ); + } + + /** + * Does this mapping have a DSL construction element or not as Boolean + */ + boolean function isDSL(){ + return ( len( variables.dsl ) GT 0 ); + } + + /** + * Set the cache properties for this mapping + * + * @key Cache key to use + * @timeout Object Timeout + * @lastAccessTimeout Object Last Access Timeout + * @provider The Cache Provider to use + */ + function setCacheProperties( + required key, + timeout ="", + lastAccessTimeout="", + provider ="default" + ){ + structAppend( variables.cache, arguments, true ); + return this; + } + + /** + * Get the cache properties struct + */ + struct function getCacheProperties(){ + return variables.cache; + } + + /** + * Add a new constructor argument to this mapping + * + * @name The name of the constructor argument (Not used for: JAVA,WEBSERVICE) + * @ref The reference mapping id this constructor argument maps to + * @dsl The construction dsl this argument references. If used, the name value must be used. + * @value The explicit value of the constructor argument, if passed. + * @javaCast The type of javaCast() to use on the value of the argument. Only used if using dsl or ref arguments + * @required If the argument is required or not, by default we assume required DI arguments + * @type The type of the argument + */ + Mapping function addDIConstructorArgument( + name, + ref, + dsl="", + value, + javaCast, + required required=true, + type ="any" + ){ + // check if already registered, if it is, just return + for ( var x = 1; x lte arrayLen( variables.DIConstructorArguments ); x++ ) { + if ( + structKeyExists( arguments, "name" ) AND + structKeyExists( variables.DIConstructorArguments[ x ], "name" ) AND + variables.DIConstructorArguments[ x ].name == arguments.name + ) { + return this; } - structAppend(def, arguments, true); - arrayAppend( instance.DIMethodArgs, def ); - return this; - - - - - - - - - - - - - - - - - - - - - - - - - var def = getDIDefinition(); - var x = 1; - // check if already registered, if it is, just return - for( x=1; x lte arrayLen( instance.DIProperties ); x++ ){ - if( instance.DIProperties[ x ].name eq arguments.name ){ return this;} + } + + // Register new constructor argument. + var defintion = getNewDIDefinition(); + structAppend( defintion, arguments, true ); + arrayAppend( variables.DIConstructorArguments, defintion ); + + return this; + } + + /** + * Add a new method argument to this mapping + * + * @name The name of the method argument (Not used for: JAVA,WEBSERVICE) + * @ref The reference mapping id this method argument maps to + * @dsl The construction dsl this argument references. If used, the name value must be used. + * @value The explicit value of the method argument, if passed. + * @javaCast The type of javaCast() to use on the value of the argument. Only used if using dsl or ref arguments + * @required If the argument is required or not, by default we assume required DI arguments + * @type The type of the argument + */ + Mapping function addDIMethodArgument( + name, + ref, + dsl, + value, + javaCast, + required required=true, + type ="any" + ){ + // check if already registered, if it is, just return + for ( var x = 1; x lte arrayLen( variables.DIMethodArguments ); x++ ) { + if ( + structKeyExists( variables.DIMethodArguments[ x ], "name" ) AND + variables.DIMethodArguments[ x ].name == arguments.name + ) { + return this; } - structAppend( def, arguments, true ); - arrayAppend( instance.DIProperties, def ); - return this; - - - - - - - - - - - - - - - - - - var def = getDIDefinition(); - var x = 1; - - // check if already registered, if it is, just return - for(x=1; x lte arrayLen(instance.DISetters); x++){ - if( instance.DISetters[ x ].name eq arguments.name ){ return this;} + } + + // Register new constructor argument. + var defintion = getNewDIDefinition(); + structAppend( defintion, arguments, true ); + arrayAppend( variables.DIMethodArguments, defintion ); + + return this; + } + + /** + * Add a new property di definition + * + * @name The name of the property to inject + * @ref The reference mapping id this property maps to + * @dsl The construction dsl this property references. If used, the name value must be used. + * @value The explicit value of the property, if passed. + * @javaCast The type of javaCast() to use on the value of the value. Only used if using dsl or ref arguments + * @scope The scope in the CFC to inject the property to. By default it will inject it to the variables scope + * @required If the property is required or not, by default we assume required DI + * @type The type of the property + */ + Mapping function addDIProperty( + required name, + ref, + dsl, + value, + javaCast, + scope ="variables", + required required=true, + type ="any" + ){ + // check if already registered, if it is, just return + for ( var x = 1; x lte arrayLen( variables.DIProperties ); x++ ) { + if ( variables.DIProperties[ x ].name eq arguments.name ) { + return this; } - // Remove scope for setter injection - def.scope = ""; - // Verify argument name, if not default it to setter name - if( NOT structKeyExists(arguments,"argName") OR len(arguments.argName) EQ 0 ){ - arguments.argName = arguments.name; + } + + var definition = getNewDIDefinition(); + structAppend( definition, arguments, true ); + arrayAppend( variables.DIProperties, definition ); + + return this; + } + + /** + * Add a new DI Setter Definition + * + * @name The name of the setter to inject + * @ref The reference mapping id this setter maps to + * @dsl The construction dsl this setter references. If used, the name value must be used. + * @value The explicit value of the setter, if passed. + * @javaCast The type of javaCast() to use on the value of the value. Only used if using dsl or ref arguments + * @argName The name of the argument to use, if not passed, we default it to the setter name + */ + Mapping function addDISetter( + required name, + ref, + dsl, + value, + javaCast, + argName + ){ + // check if already registered, if it is, just return + for ( var x = 1; x lte arrayLen( variables.DISetters ); x++ ) { + if ( variables.DISetters[ x ].name eq arguments.name ) { + return this; } - // save incoming params - structAppend(def, arguments, true); - // save new DI setter injection - arrayAppend( instance.DISetters, def ); + } + + // Get new definition + var definition = getNewDIDefinition(); + // Remove scope for setter injection + definition.scope = ""; + // Verify argument name, if not default it to setter name + if ( NOT structKeyExists( arguments, "argName" ) OR len( arguments.argName ) EQ 0 ) { + arguments.argName = arguments.name; + } + // save incoming params + structAppend( definition, arguments, true ); + // save new DI setter injection + arrayAppend( variables.DISetters, definition ); + + return this; + } + + /** + * Checks if this mapping has already been processed or not + */ + boolean function isDiscovered(){ + return variables.discovered; + } + + /** + * Is this mapping eager initialized or not as Boolean + */ + boolean function isEagerInit(){ + return ( isBoolean( variables.eagerInit ) ? variables.eagerInit : false ); + } + + /** + * Add a new provider method to this mapping + * + * @method The provided method to override as a provider + * @mapping The mapping to provide via the selected method + */ + Mapping function addProviderMethod( required method, required mapping ){ + arrayAppend( variables.providerMethods, arguments ); + return this; + } + + /** + * --------------------------------------------------- + * Processing Methods + * --------------------------------------------------- + */ + + /** + * Process a mapping for metadata discovery and more + * + * @binder The binder requesting the processing + * @injector The calling injector processing the mappping + * @metadata The metadata of an a-la-carte processing, use instead of retrieveing again + * + * @return Mapping + */ + Mapping function process( + required binder, + required injector, + metadata + ){ + var md = variables.objectMetadata; + var eventManager = arguments.injector.getEventManager(); + var cacheProperties = {}; + + // Short circuit, if mapping already discovered, then just exit out. + if( variables.discovered ){ return this; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if( NOT instance.discovered ){ - // announce inspection - iData = {mapping=this,binder=arguments.binder,injector=arguments.binder.getInjector()}; - eventManager.announce("beforeInstanceInspection",iData); - - // Processing only done for CFC's,rest just mark and return - if( instance.type neq arguments.binder.TYPES.CFC ){ - if( NOT len(instance.scope) ){ instance.scope = "noscope"; } - if( NOT len(instance.autowire) ){ instance.autowire = true; } - if( NOT len(instance.eagerInit) ){ instance.eagerInit = false; } - if( NOT len(instance.threadSafe) ){ instance.threadSafe = false; } - // finished processing mark as discovered - instance.discovered = true; - // announce it - eventManager.announce("afterInstanceInspection",iData); - return; - } - - // Get the instance's metadata first, so we can start processing. - if( structKeyExists(arguments,"metadata") ){ - md = arguments.metadata; - } - else{ - var produceMetadataUDF = function() { return injector.getUtil().getInheritedMetaData(instance.path, binder.getStopRecursions()); }; - - // Are we caching metadata? - if( len( binder.getMetadataCache() ) ) { - // Get from cache or produce on demand - md = injector.getCacheBox().getCache( binder.getMetadataCache() ).getOrSet( - instance.path, - produceMetadataUDF - ); - } else { - md = produceMetadataUDF(); - } - } - - // Store Metadata - instance.metadata = md; + } + + // Generate a lock token + if ( isSimpleValue( variables.path ) ){ + var lockToken = variables.path; + } else { + var lockToken = createUUID(); + } + + // Lock for discovery based on path location, only done once per mapping + lock + name ="Mapping.#arguments.injector.getInjectorID()#.MetadataProcessing.#lockToken#" + type ="exclusive" + timeout ="20" + throwOnTimeout="true" + { + // announce inspection + var iData = { + mapping : this, + binder : arguments.binder, + injector : arguments.binder.getInjector() + }; + eventManager.announce( "beforeInstanceInspection", iData ); - // Process persistence if not set already by configuration as it takes precedence - if( NOT len(instance.scope) ){ - // Singleton Processing - if( structKeyExists(md,"singleton") ){ instance.scope = arguments.binder.SCOPES.SINGLETON; } - // Registered Scope Processing - if( structKeyExists(md,"scope") ){ instance.scope = md.scope; } - // CacheBox scope processing if cachebox annotation found, or cache annotation found - if( structKeyExists(md,"cacheBox") OR ( structKeyExists(md,"cache") AND isBoolean(md.cache) AND md.cache ) ){ - instance.scope = arguments.binder.SCOPES.CACHEBOX; - } + // Processing only done for CFC's,rest just mark and return + if ( variables.type neq arguments.binder.TYPES.CFC ) { + if ( NOT len( variables.scope ) ) { + variables.scope = "noscope"; + } + if ( NOT len( variables.autowire ) ) { + variables.autowire = true; + } + if ( NOT len( variables.eagerInit ) ) { + variables.eagerInit = false; + } + if ( NOT len( variables.threadSafe ) ) { + variables.threadSafe = false; + } + // finished processing mark as discovered + variables.discovered = true; + // announce it + eventManager.announce( "afterInstanceInspection", iData ); + return this; + } - // check if scope found? If so, then set it to no scope. - if( NOT len(instance.scope) ){ instance.scope = "noscope"; } + // Get the metadata first, so we can start processing. + if ( structKeyExists( arguments, "metadata" ) ) { + md = arguments.metadata; + } else { + // Are we caching metadata? or just using it + if ( len( arguments.binder.getMetadataCache() ) ) { + // Get from cache or produce on demand + md = arguments.injector + .getCacheBox() + .getCache( arguments.binder.getMetadataCache() ) + .getOrSet( variables.path, produceMetadataUDF( arguments.injector, arguments.binder ) ); + } else { + md = produceMetadataUDF( arguments.injector, arguments.binder ); + } + } - } // end of persistence checks + // Store Metadata + variables.objectMetadata = md; - // Cachebox Persistence Processing - if( instance.scope EQ arguments.binder.SCOPES.CACHEBOX ){ - // Check if we already have a key, maybe added via configuration - if( NOT len( instance.cache.key ) ){ - instance.cache.key = "wirebox-#instance.name#"; - } - // Check the default provider now to see if set by configuration - if( NOT len( instance.cache.provider) ){ - // default it first - instance.cache.provider = "default"; - // Now check the annotations for the provider - if( structKeyExists(md,"cacheBox") AND len(md.cacheBox) ){ - instance.cache.provider = md.cacheBox; - } - } - // Check if timeouts set by configuration or discovery - if( NOT len( instance.cache.timeout ) ){ - // Discovery by annocations - if( structKeyExists(md,"cachetimeout") AND isNumeric(md.cacheTimeout) ){ - instance.cache.timeout = md.cacheTimeout; - } - } - // Check if lastAccessTimeout set by configuration or discovery - if( NOT len( instance.cache.lastAccessTimeout ) ){ - // Discovery by annocations - if( structKeyExists(md,"cacheLastAccessTimeout") AND isNumeric(md.cacheLastAccessTimeout) ){ - instance.cache.lastAccessTimeout = md.cacheLastAccessTimeout; - } - } + // Process persistence if not set already by configuration as it takes precedence + if ( NOT len( variables.scope ) ) { + // Singleton Processing + if ( structKeyExists( md, "singleton" ) ) { + variables.scope = arguments.binder.SCOPES.SINGLETON; } - - // Alias annotations if found, then append them as aliases. - if( structKeyExists(md, "alias") ){ - thisAliases = listToArray(md.alias); - instance.alias.addAll( thisAliases ); - // register alias references on binder - for(x=1; x lte arrayLen(thisAliases); x++){ - mappings[ thisAliases[ x ] ] = this; - } + // Registered Scope Processing + if ( structKeyExists( md, "scope" ) ) { + variables.scope = md.scope; + } + // CacheBox scope processing if cachebox annotation found, or cache annotation found + if ( + structKeyExists( md, "cacheBox" ) OR ( + structKeyExists( md, "cache" ) AND isBoolean( md.cache ) AND md.cache + ) + ) { + variables.scope = arguments.binder.SCOPES.CACHEBOX; } - // eagerInit annotation - if( NOT len(instance.eagerInit) ){ - if( structKeyExists(md,"eagerInit") ){ - instance.eagerInit = true; - } - else{ - // defaults to lazy loading - instance.eagerInit = false; - } + // check if scope found? If so, then set it to no scope. + if ( NOT len( variables.scope ) ) { + variables.scope = "noscope"; } + } + // end of persistence checks - // threadSafe wiring annotation - if( NOT len(instance.threadSafe) ){ - if( structKeyExists(md,"threadSafe") AND NOT len(md.threadSafe)){ - instance.threadSafe = true; + // Cachebox Persistence Processing + if ( variables.scope EQ arguments.binder.SCOPES.CACHEBOX ) { + // Check if we already have a key, maybe added via configuration + if ( NOT len( variables.cache.key ) ) { + variables.cache.key = "wirebox-#variables.name#"; + } + // Check the default provider now to see if set by configuration + if ( NOT len( variables.cache.provider ) ) { + // default it first + variables.cache.provider = "default"; + // Now check the annotations for the provider + if ( structKeyExists( md, "cacheBox" ) AND len( md.cacheBox ) ) { + variables.cache.provider = md.cacheBox; } - else if( structKeyExists(md,"threadSafe") AND len(md.threadSafe) AND isBoolean(md.threadSafe) ){ - instance.threadSafe = md.threadSafe; + } + // Check if timeouts set by configuration or discovery + if ( NOT len( variables.cache.timeout ) ) { + // Discovery by annocations + if ( structKeyExists( md, "cachetimeout" ) AND isNumeric( md.cacheTimeout ) ) { + variables.cache.timeout = md.cacheTimeout; } - else{ - // defaults to non thread safe wiring - instance.threadSafe = false; + } + // Check if lastAccessTimeout set by configuration or discovery + if ( NOT len( variables.cache.lastAccessTimeout ) ) { + // Discovery by annocations + if ( + structKeyExists( md, "cacheLastAccessTimeout" ) AND isNumeric( + md.cacheLastAccessTimeout + ) + ) { + variables.cache.lastAccessTimeout = md.cacheLastAccessTimeout; } } + } - // mixins annotation only if not overriden - if( NOT arrayLen(instance.mixins) ){ - if( structKeyExists(md,"mixins") ){ - instance.mixins = listToArray( md.mixins ); - } + // Alias annotations if found, then append them as aliases. + if ( structKeyExists( md, "alias" ) ) { + var thisAliases = listToArray( md.alias ); + variables.alias.addAll( thisAliases ); + // register alias references on binder + var mappings = arguments.binder.getMappings(); + for ( var x = 1; x lte arrayLen( thisAliases ); x++ ) { + mappings[ thisAliases[ x ] ] = this; } + } - // check if the autowire NOT set, so we can discover it. - if( NOT len(instance.autowire) ){ - // Check if autowire annotation found or autowire already set - if( structKeyExists(md,"autowire") and isBoolean(md.autowire) ){ - instance.autoWire = md.autowire; - } - else{ - // default to true - instance.autoWire = true; - } + // eagerInit annotation only if not overriden + if ( NOT len( variables.eagerInit ) ) { + if ( structKeyExists( md, "eagerInit" ) ) { + variables.eagerInit = true; + } else { + // defaults to lazy loading + variables.eagerInit = false; } + } - // look for parent metadata on the instance referring to an abstract parent (by alias) to copy - // dependencies and definitions from - if( structKeyExists(md, "parent") and len(trim(md.parent))){ - arguments.binder.parent(alias:md.parent); + // threadSafe wiring annotation + if ( NOT len( variables.threadSafe ) ) { + if ( structKeyExists( md, "threadSafe" ) AND NOT len( md.threadSafe ) ) { + variables.threadSafe = true; + } else if ( + structKeyExists( md, "threadSafe" ) AND len( md.threadSafe ) AND isBoolean( md.threadSafe ) + ) { + variables.threadSafe = md.threadSafe; + } else { + // defaults to non thread safe wiring + variables.threadSafe = false; } + } - // Only process if autowiring - if( instance.autoWire ){ - // Process Methods, Constructors and Properties only if non autowire annotation check found on component. - processDIMetadata( arguments.binder, md ); + // mixins annotation only if not overriden + if ( NOT arrayLen( variables.mixins ) ) { + if ( structKeyExists( md, "mixins" ) ) { + variables.mixins = listToArray( md.mixins ); } + } - // AOP AutoBinding only if both @classMatcher and @methodMatcher exist - if( isAspectAutoBinding() AND structKeyExists(md,"classMatcher") AND structKeyExists(md,"methodMatcher") ){ - processAOPBinding( arguments.binder, md); + // autowire only if not overriden + if ( NOT len( variables.autowire ) ) { + // Check if autowire annotation found or autowire already set + if ( structKeyExists( md, "autowire" ) and isBoolean( md.autowire ) ) { + variables.autoWire = md.autowire; + } else { + // default to true + variables.autoWire = true; } + } - // finished processing mark as discovered - instance.discovered = true; + // look for parent metadata referring to an abstract parent (by alias) to copy + // dependencies and definitions from + if ( structKeyExists( md, "parent" ) and len( trim( md.parent ) ) ) { + arguments.binder.parent( alias : md.parent ); + } - // announce it - eventManager.announce("afterInstanceInspection",iData); + // Only process if autowiring + if ( variables.autoWire ) { + // Process Methods, Constructors and Properties only if non autowire annotation check found on component. + processDIMetadata( arguments.binder, md ); } - - - - - - - - - - - - var classes = listFirst(arguments.metadata.classMatcher,":"); - var methods = listFirst(arguments.metadata.methodMatcher,":"); - var classMatcher = ""; - var methodMatcher = ""; - - // determine class matching - switch(classes){ - case "any" : { classMatcher = arguments.binder.match().any(); break; } - case "annotatedWith" : { - // annotation value? - if( listLen(arguments.metadata.classMatcher,":") eq 3 ){ - classMatcher = arguments.binder.match().annotatedWith( getToken(arguments.metadata.classMatcher,2,":"), getToken(arguments.metadata.classMatcher,3,":") ); - } - // No annotation value - else{ - classMatcher = arguments.binder.match().annotatedWith( getToken(arguments.metadata.classMatcher,2,":") ); - } - break; + + // AOP AutoBinding only if both @classMatcher and @methodMatcher exist + if ( + isAspectAutoBinding() AND structKeyExists( md, "classMatcher" ) AND structKeyExists( + md, + "methodMatcher" + ) + ) { + processAOPBinding( arguments.binder, md ); + } + + // finished processing mark as discovered + variables.discovered = true; + + // announce it + eventManager.announce( "afterInstanceInspection", iData ); + } // End lock + + return this; + } + + /** + * --------------------------------------------------- + * Private Methods + * --------------------------------------------------- + */ + + /** + * Produce metadata helper + * + * @injector The injector to use + * @binder The binder to use + * + * @return Metadata struct + */ + private function produceMetadataUDF( required injector, required binder ){ + return arguments + .injector + .getUtil() + .getInheritedMetaData( variables.path, arguments.binder.getStopRecursions() ); + }; + + /** + * Process the AOP self binding aspects + * + * @binder The binder requesting the processing + * @metadata The metadata to process + * + * @return Mapping + */ + private Mapping function processAOPBinding( required binder, required metadata ){ + var classes = listFirst( arguments.metadata.classMatcher, ":" ); + var methods = listFirst( arguments.metadata.methodMatcher, ":" ); + var classMatcher = ""; + var methodMatcher = ""; + + // determine class matching + switch ( classes ) { + case "any": { + classMatcher = arguments.binder.match().any(); + break; + } + case "annotatedWith": { + // annotation value? + if ( listLen( arguments.metadata.classMatcher, ":" ) eq 3 ) { + classMatcher = arguments.binder + .match() + .annotatedWith( + getToken( arguments.metadata.classMatcher, 2, ":" ), + getToken( arguments.metadata.classMatcher, 3, ":" ) + ); } - case "mappings" : { classMatcher = arguments.binder.match().mappings( getToken(arguments.metadata.classMatcher,2,":") ); break; } - case "instanceOf" : { classMatcher = arguments.binder.match().instanceOf( getToken(arguments.metadata.classMatcher,2,":") ); break; } - case "regex" : { classMatcher = arguments.binder.match().regex( getToken(arguments.metadata.classMatcher,2,":") ); break; } - default: { - // throw, no matching matchers - throw(message="Invalid Class Matcher: #classes#", - type="Mapping.InvalidAOPClassMatcher", - detail="Valid matchers are 'any,annotatedWith:annotation,annotatedWith:annotation:value,mappings:XXX,instanceOf:XXX,regex:XXX'"); + // No annotation value + else { + classMatcher = arguments.binder + .match() + .annotatedWith( getToken( arguments.metadata.classMatcher, 2, ":" ) ); } + break; + } + case "mappings": { + classMatcher = arguments.binder + .match() + .mappings( getToken( arguments.metadata.classMatcher, 2, ":" ) ); + break; + } + case "instanceOf": { + classMatcher = arguments.binder + .match() + .instanceOf( getToken( arguments.metadata.classMatcher, 2, ":" ) ); + break; } + case "regex": { + classMatcher = arguments.binder + .match() + .regex( getToken( arguments.metadata.classMatcher, 2, ":" ) ); + break; + } + default: { + // throw, no matching matchers + throw( + message = "Invalid Class Matcher: #classes#", + type = "Mapping.InvalidAOPClassMatcher", + detail = "Valid matchers are 'any,annotatedWith:annotation,annotatedWith:annotation:value,mappings:XXX,instanceOf:XXX,regex:XXX'" + ); + } + } - // determine method matching - switch(methods){ - case "any" : { methodMatcher = arguments.binder.match().any(); break; } - case "annotatedWith" : { - // annotation value? - if( listLen(arguments.metadata.classMatcher,":") eq 3 ){ - methodMatcher = arguments.binder.match().annotatedWith( getToken(arguments.metadata.methodMatcher,2,":"), getToken(arguments.metadata.methodMatcher,3,":") ); - } - // No annotation value - else{ - methodMatcher = arguments.binder.match().annotatedWith( getToken(arguments.metadata.methodMatcher,2,":") ); - } - break; + // determine method matching + switch ( methods ) { + case "any": { + methodMatcher = arguments.binder.match().any(); + break; + } + case "annotatedWith": { + // annotation value? + if ( listLen( arguments.metadata.classMatcher, ":" ) eq 3 ) { + methodMatcher = arguments.binder + .match() + .annotatedWith( + getToken( arguments.metadata.methodMatcher, 2, ":" ), + getToken( arguments.metadata.methodMatcher, 3, ":" ) + ); } - case "methods" : { methodMatcher = arguments.binder.match().methods( getToken(arguments.metadata.methodMatcher,2,":") ); break; } - case "instanceOf" : { methodMatcher = arguments.binder.match().instanceOf( getToken(arguments.metadata.methodMatcher,2,":") ); break; } - case "regex" : { methodMatcher = arguments.binder.match().regex( getToken(arguments.metadata.methodMatcher,2,":") ); break; } - default: { - // throw, no matching matchers - throw(message="Invalid Method Matcher: #classes#", - type="Mapping.InvalidAOPMethodMatcher", - detail="Valid matchers are 'any,annotatedWith:annotation,annotatedWith:annotation:value,methods:XXX,instanceOf:XXX,regex:XXX'"); + // No annotation value + else { + methodMatcher = arguments.binder + .match() + .annotatedWith( getToken( arguments.metadata.methodMatcher, 2, ":" ) ); } + break; } - - // Bind the Aspect to this Mapping - arguments.binder.bindAspect(classMatcher,methodMatcher,getName()); - - - - - - - - - - var x = 1; - var y = 1; - var md = arguments.metadata; - var fncLen = 0; - var params = ""; - - // Look For properties for annotation injections - if( structKeyExists(md,"properties") and ArrayLen(md.properties) GT 0){ - // Loop over each property and identify injectable properties - for(x=1; x lte ArrayLen(md.properties); x=x+1 ){ - // Check if property not discovered or if inject annotation is found - if( structKeyExists(md.properties[ x ],"inject") ){ - // prepare default params, we do this so we do not alter the md as it is cached by cf - params = { - scope="variables", inject="model", name=md.properties[ x ].name, required=true, type="any" - }; - // default property type - if( structKeyExists( md.properties[ x ], "type" ) ){ - params.type = md.properties[ x ].type; - } - // default injection scope, if not found in object - if( structKeyExists(md.properties[ x ],"scope") ){ - params.scope = md.properties[ x ].scope; - } - // Get injection if it exists - if( len(md.properties[ x ].inject) ){ - params.inject = md.properties[ x ].inject; - } - // Get required - if( structKeyExists( md.properties[ x ], "required" ) and isBoolean( md.properties[ x ].required ) ){ - params.required = md.properties[ x ].required; + case "methods": { + methodMatcher = arguments.binder + .match() + .methods( getToken( arguments.metadata.methodMatcher, 2, ":" ) ); + break; + } + case "instanceOf": { + methodMatcher = arguments.binder + .match() + .instanceOf( getToken( arguments.metadata.methodMatcher, 2, ":" ) ); + break; + } + case "regex": { + methodMatcher = arguments.binder + .match() + .regex( getToken( arguments.metadata.methodMatcher, 2, ":" ) ); + break; + } + default: { + // throw, no matching matchers + throw( + message = "Invalid Method Matcher: #classes#", + type = "Mapping.InvalidAOPMethodMatcher", + detail = "Valid matchers are 'any,annotatedWith:annotation,annotatedWith:annotation:value,methods:XXX,instanceOf:XXX,regex:XXX'" + ); + } + } + + // Bind the Aspect to this Mapping + arguments.binder.bindAspect( classMatcher, methodMatcher, getName() ); + + return this; + } + + /** + * Process methods/properties for dependency injection + * + * @binder The binder requesting the processing + * @metadata The metadata to process + * @dependencies The dependencies structure + * + * @return Mapping + */ + private Mapping function processDIMetadata( required binder, required metadata, dependencies={} ){ + // Shortcut + var md = arguments.metadata; + + // Look For properties for annotation injections and register them with the mapping + param md.properties = []; + md.properties + // Only process injectable properties + .filter( function( thisProperty ) { + return structKeyExists( thisProperty, "inject" ); + } ) + // Process each property + .each( function( thisProperty ){ + addDIProperty( + name : arguments.thisProperty.name, + dsl : ( len( arguments.thisProperty.inject ) ? arguments.thisProperty.inject : "model" ), + scope : ( structKeyExists( arguments.thisProperty, "scope" ) ? arguments.thisProperty.scope : "variables" ), + required : ( structKeyExists( arguments.thisProperty, "required" ) ? arguments.thisProperty.required : true ), + type : ( structKeyExists( arguments.thisProperty, "type" ) ? arguments.thisProperty.type : "any" ) + ); + } ); + + // Look For functions for setter injections and more and register them with the mapping + param md.functions = []; + md.functions + // Verify Processing or do we continue to next iteration for processing + // This is to avoid overriding by parent trees in inheritance chains + .filter( function( thisFunction ) { + return !structKeyExists( dependencies, thisFunction.name ); + } ) + .each( function( thisFunction ){ + // Constructor Processing if found + if ( thisFunction.name eq variables.constructor ) { + // Process parameters for constructor injection + for( var thisParam in thisFunction.parameters ){ + // Check injection annotation, if not found then no injection + if ( structKeyExists( thisParam, "inject" ) ) { + // ADD Constructor argument + addDIConstructorArgument( + name : thisParam.name, + dsl : ( len( thisParam.inject ) ? thisParam.inject : "model" ), + required : ( structKeyExists( thisParam, "required" ) ? thisParam.required : false ), + type : ( structKeyExists( thisParam, "type" ) ? thisParam.type : "any" ) + ); } - // Add to property to mappings - addDIProperty( name=params.name, dsl=params.inject, scope=params.scope, required=params.required, type=params.type ); } - + // add constructor to found list, so it is processed only once in recursions + dependencies[ thisFunction.name ] = "constructor"; } - }//end DI properties - - // Method DI discovery - if( structKeyExists(md, "functions") ){ - fncLen = arrayLen(md.functions); - for(x=1; x lte fncLen; x++ ){ - - // Verify Processing or do we continue to next iteration for processing - // This is to avoid overriding by parent trees in inheritance chains - if( structKeyExists(arguments.dependencies, md.functions[ x ].name) ){ - continue; - } - // Constructor Processing if found - if( md.functions[ x ].name eq instance.constructor ){ - // Loop Over Arguments to process them for dependencies - for(y=1;y lte arrayLen(md.functions[ x ].parameters); y++){ - - // prepare params as we do not alter md as cf caches it - params = { - required = false, inject="model", name=md.functions[ x ].parameters[y].name, type="any" - }; - // check type annotation - if( structKeyExists( md.functions[ x ].parameters[ y ], "type" ) ){ - params.type = md.functions[ x ].parameters[ y ].type; - } - // Check required annotation - if( structKeyExists(md.functions[ x ].parameters[y], "required") ){ - params.required = md.functions[ x ].parameters[y].required; - } - // Check injection annotation, if not found then no injection - if( structKeyExists(md.functions[ x ].parameters[y],"inject") ){ - - // Check if inject has value, else default it to 'model' or 'id' namespace - if( len(md.functions[ x ].parameters[y].inject) ){ - params.inject = md.functions[ x ].parameters[y].inject; - } - - // ADD Constructor argument - addDIConstructorArgument(name=params.name, - dsl=params.inject, - required=params.required, - type=params.type); - } - - } - // add constructor to found list, so it is processed only once in recursions - arguments.dependencies[md.functions[ x ].name] = "constructor"; - } - - // Setter discovery, MUST be inject annotation marked to be processed. - if( left(md.functions[ x ].name,3) eq "set" AND structKeyExists(md.functions[ x ],"inject")){ - - // setup setter params in order to avoid touching the md struct as cf caches it - params = {inject="model",name=right(md.functions[ x ].name, Len(md.functions[ x ].name)-3)}; - - // Check DSL marker if it has a value else use default of Model - if( len(md.functions[ x ].inject) ){ - params.inject = md.functions[ x ].inject; - } - // Add to setter to mappings and recursion lookup - addDISetter(name=params.name,dsl=params.inject); - arguments.dependencies[md.functions[ x ].name] = "setter"; - } - - // Provider Methods Discovery - if( structKeyExists( md.functions[ x ], "provider") AND len(md.functions[ x ].provider)){ - addProviderMethod(md.functions[ x ].name, md.functions[ x ].provider); - arguments.dependencies[md.functions[ x ].name] = "provider"; - } - - // onDIComplete Method Discovery - if( structKeyExists( md.functions[ x ], "onDIComplete") ){ - arrayAppend(instance.onDIComplete, md.functions[ x ].name ); - arguments.dependencies[md.functions[ x ].name] = "onDIComplete"; - } - - }//end loop of functions - }//end if functions found + // Setter discovery, MUST be inject annotation marked to be processed. + if ( left( thisFunction.name, 3 ) eq "set" AND structKeyExists( thisFunction, "inject" ) ) { + // Add to setter to mappings and recursion lookup + addDISetter( + name : right( thisFunction.name, len( thisFunction.name ) - 3 ), + dsl : ( len( thisFunction.inject ) ? thisFunction.inject : "model" ) + ); + dependencies[ thisFunction.name ] = "setter"; + } - - + // Provider Methods Discovery + if ( structKeyExists( thisFunction, "provider" ) AND len( thisFunction.provider ) ) { + addProviderMethod( thisFunction.name, thisFunction.provider ); + dependencies[ thisFunction.name ] = "provider"; + } - - - - + // onDIComplete Method Discovery + if ( structKeyExists( thisFunction, "onDIComplete" ) ) { + arrayAppend( variables.onDIComplete, thisFunction.name ); + dependencies[ thisFunction.name ] = "onDIComplete"; + } - \ No newline at end of file + } ); // End function processing + + return this; + } + + /** + * Get a new DI definition structure + */ + private struct function getNewDIDefinition(){ + return { + "name" : "", + "value" : javacast( "null", "" ), + "dsl" : javacast( "null", "" ), + "scope" : "variables", + "javaCast" : javacast( "null", "" ), + "ref" : javacast( "null", "" ), + "required" : false, + "argName" : "", + "type" : "any" + }; + } +} \ No newline at end of file diff --git a/system/ioc/config/Mixin.cfc b/system/ioc/config/Mixin.cfc index 1651756dd..e989983ba 100644 --- a/system/ioc/config/Mixin.cfc +++ b/system/ioc/config/Mixin.cfc @@ -1,11 +1,10 @@ -component{ +component { function $init( required mixins ){ - // Include the mixins - for( var thisMixin in arguments.mixins ){ + for ( var thisMixin in arguments.mixins ) { thisMixin = trim( thisMixin ); - if( listLast( thisMixin, "." ) != "cfm" ){ + if ( listLast( thisMixin, "." ) != "cfm" ) { include "#thisMixin#.cfm"; } else { include "#thisMixin#"; @@ -13,13 +12,13 @@ component{ } // Expose them - for( var key in variables ){ - if( isCustomFunction( variables[ key ] ) AND !structKeyExists( this, key ) ){ + for ( var key in variables ) { + if ( isCustomFunction( variables[ key ] ) AND !structKeyExists( this, key ) ) { this[ key ] = variables[ key ]; } } return this; } - -} \ No newline at end of file + +} diff --git a/system/logging/AbstractAppender.cfc b/system/logging/AbstractAppender.cfc index 04ef2a2dd..f8d04b4f1 100644 --- a/system/logging/AbstractAppender.cfc +++ b/system/logging/AbstractAppender.cfc @@ -268,18 +268,27 @@ component accessors="true"{ if ( !isActive ) { variables.lock( function(){ if ( !variables.logListener.active ) { - // Mark listener as activated - //out( "(#getName()#) ScheduleTask needs to be started..." ); - variables.logListener.active = true; // Create the runnable Log Listener, Start it up baby! - variables.logBox - .getTaskScheduler() - .schedule( - task = this, - method = "runLogListener", - loadAppContext = false - ); + try{ + variables.logBox + .getTaskScheduler() + .schedule( + task = this, + method = "runLogListener", + loadAppContext = false + ); + + // Mark listener as activated + //out( "(#getName()#) ScheduleTask needs to be started..." ); + variables.logListener.active = true; + } catch( any e ){ + // Just in case it doesn't start, just skip it for now and let another thread + // kick start it. We will just log the exception just in case + // Usually these exceptions can be on shutdowns or when the scheduler cannot take + // any more tasks. + out( "Error scheduling log listener: #e.message# #e.detail#" ); + } //out( "(#getName()#) ScheduleTask started" ); } @@ -290,8 +299,10 @@ component accessors="true"{ /** * Executed by our schedule tasks to move the queue elements into the appender's implemented * destination + * + * @force This forces a flush with no waiting, usually called synchronously by a shutdown */ - function runLogListener(){ + function runLogListener( force = false ){ try { // Create a queue context for queue processing data var queueContext = { @@ -300,7 +311,8 @@ component accessors="true"{ "maxIdle" : 15000, "sleepInterval" : 25, "count" : 0, - "hasMessages" : false + "hasMessages" : false, + "force" : arguments.force }; // Init Message @@ -309,7 +321,7 @@ component accessors="true"{ // Start Advice onLogListenerStart( queueContext ); - while ( variables.logListener.queue.len() || queueContext.lastRun + queueContext.maxIdle > getTickCount() ) { + while ( arguments.force || variables.logListener.queue.len() || queueContext.lastRun + queueContext.maxIdle > getTickCount() ) { // out( "len: #variables.logListener.queue.len()# last run: #lastRun# idle: #queueContext.maxIdle#" ); if ( variables.logListener.queue.len() ) { @@ -330,8 +342,8 @@ component accessors="true"{ // Advice we are about to go to sleep onLogListenerSleep( queueContext ); - // Only take a nap if we've nothing to do - if( !variables.logListener.queue.len() ) { + // Only take a nap if we've nothing to do and we are not in force mode + if( !arguments.force && !variables.logListener.queue.len() ) { sleep( queueContext.sleepInterval ); // take a nap } } @@ -348,10 +360,12 @@ component accessors="true"{ // Advice //out( "Stopping Log listener task for (#getName()#), it ran for #getTickCount() - queueContext.start#ms!" ); - // Stop log listener - variables.lock( function(){ - variables.logListener.active = false; - } ); + // Stop log listener only if not in force mode + if( !arguments.force ){ + variables.lock( function(){ + variables.logListener.active = false; + } ); + } } } @@ -407,6 +421,12 @@ component accessors="true"{ return this; } + /** + * Each appender can shut itself down if needed. This callback is done by the LogBox engine during reinits or shutdowns + */ + function shutdown(){ + } + /****************************************** PRIVATE *********************************************/ /** diff --git a/system/logging/LogBox.cfc b/system/logging/LogBox.cfc index cc2b864e2..ac4c7ce69 100644 --- a/system/logging/LogBox.cfc +++ b/system/logging/LogBox.cfc @@ -161,6 +161,27 @@ component accessors="true"{ } } + /** + * Shutdown the injector gracefully by calling the shutdown events internally. + **/ + function shutdown(){ + + // Check if config has onShutdown convention + if( structKeyExists( variables.config, "onShutdown" ) ){ + variables.config.onShutdown( this ); + } + + // Shutdown Executors if not in ColdBox Mode + if( !isObject( variables.coldbox ) ){ + variables.asyncManager.shutdownAllExecutors( force = true ); + } + + // Shutdown appenders + variables.appenderRegistry.each( function( key, appender ){ + arguments.appender.shutdown(); + } ); + } + /** * Get the root logger object * diff --git a/system/logging/appenders/FileAppender.cfc b/system/logging/appenders/FileAppender.cfc index 24387d801..8a3b44268 100644 --- a/system/logging/appenders/FileAppender.cfc +++ b/system/logging/appenders/FileAppender.cfc @@ -105,7 +105,7 @@ component accessors="true" extends="coldbox.system.logging.AbstractAppender"{ message = replace( message, chr(13), ' ', "all" ); // Entry string - entry = '"#severityToString( logEvent.getSeverity() )#","#getname()#","#dateformat( timestamp, "MM/DD/YYYY" )#","#timeformat( timestamp, "HH:MM:SS" )#","#loge.getCategory()#","#message#"'; + entry = '"#severityToString( logEvent.getSeverity() )#","#getname()#","#dateformat( timestamp, "mm/dd/yyyy" )#","#timeformat( timestamp, "HH:mm:ss" )#","#loge.getCategory()#","#message#"'; } // Queue it up @@ -143,7 +143,7 @@ component accessors="true" extends="coldbox.system.logging.AbstractAppender"{ // Default Log Directory ensureDefaultLogDirectory(); // Create log file - fileWrite( variables.logFullPath, '"Severity","Appender","Date","Time","Category","Message"' ); + fileWrite( variables.logFullPath, '"Severity","Appender","Date","Time","Category","Message"#chr( 13 )##chr( 10 )#' ); } catch( Any e ) { $log( "ERROR", "Cannot create appender's: #getName()# log file. File #variables.logFullpath#. #e.message# #e.detail#" ); } @@ -177,9 +177,10 @@ component accessors="true" extends="coldbox.system.logging.AbstractAppender"{ * @queueContext A struct of data attached to this processing queue thread */ function onLogListenerSleep( required struct queueContext ){ + var isFlushNeeded = ( arguments.queueContext.start + arguments.queueContext.flushInterval < getTickCount() ) || arguments.queueContext.force; // flush to disk every start + 1000ms if( - arguments.queueContext.start + arguments.queueContext.flushInterval < getTickCount() + isFlushNeeded && !isSimpleValue( arguments.queueContext.oFile ) ){ @@ -227,6 +228,13 @@ component accessors="true" extends="coldbox.system.logging.AbstractAppender"{ } } + /** + * Process a shutdown! + */ + function shutdown(){ + //runLogListener( force = true ); + } + /************************************ PRIVATE ************************************/ /** @@ -242,4 +250,4 @@ component accessors="true" extends="coldbox.system.logging.AbstractAppender"{ return this; } -} \ No newline at end of file +} diff --git a/system/testing/BaseTestCase.cfc b/system/testing/BaseTestCase.cfc index 79a991cda..5d10a8d45 100755 --- a/system/testing/BaseTestCase.cfc +++ b/system/testing/BaseTestCase.cfc @@ -22,6 +22,15 @@ component extends="testbox.system.compat.framework.TestCase" accessors="true" { * The application key for the ColdBox applicatin this test links to */ property name="coldboxAppKey"; + /** + * If in integration mode, you can tag for your tests to be automatically autowired with dependencies + * by WireBox + */ + property name="autowire" type="boolean" default="false"; + /** + * The test case metadata + */ + property name="metadata" type="struct"; // Public Switch Properties // TODO: Remove by ColdBox 4.2+ and move to variables scope. @@ -33,6 +42,8 @@ component extends="testbox.system.compat.framework.TestCase" accessors="true" { variables.configMapping = ""; variables.controller = ""; variables.coldboxAppKey = "cbController"; + variables.autowire = false; + variables.metadata = {}; /********************************************* LIFE-CYCLE METHODS *********************************************/ @@ -41,26 +52,30 @@ component extends="testbox.system.compat.framework.TestCase" accessors="true" { * @return BaseTestCase */ function metadataInspection(){ - var md = new coldbox.system.core.util.Util().getInheritedMetadata( this ); + variables.metadata = new coldbox.system.core.util.Util().getInheritedMetadata( this ); // Inspect for appMapping annotation - if ( structKeyExists( md, "appMapping" ) ) { - variables.appMapping = md.appMapping; + if ( structKeyExists( variables.metadata, "appMapping" ) ) { + variables.appMapping = variables.metadata.appMapping; } // Configuration File mapping - if ( structKeyExists( md, "configMapping" ) ) { - variables.configMapping = md.configMapping; + if ( structKeyExists( variables.metadata, "configMapping" ) ) { + variables.configMapping = variables.metadata.configMapping; } // ColdBox App Key - if ( structKeyExists( md, "coldboxAppKey" ) ) { - variables.coldboxAppKey = md.coldboxAppKey; + if ( structKeyExists( variables.metadata, "coldboxAppKey" ) ) { + variables.coldboxAppKey = variables.metadata.coldboxAppKey; } // Load coldBox annotation - if ( structKeyExists( md, "loadColdbox" ) ) { - this.loadColdbox = md.loadColdbox; + if ( structKeyExists( variables.metadata, "loadColdbox" ) ) { + this.loadColdbox = variables.metadata.loadColdbox; } // unLoad coldBox annotation - if ( structKeyExists( md, "unLoadColdbox" ) ) { - this.unLoadColdbox = md.unLoadColdbox; + if ( structKeyExists( variables.metadata, "unLoadColdbox" ) ) { + this.unLoadColdbox = variables.metadata.unLoadColdbox; + } + // autowire + if ( structKeyExists( variables.metadata, "autowire" ) ) { + this.autowire = variables.metadata.autowire; } return this; } @@ -113,10 +128,18 @@ component extends="testbox.system.compat.framework.TestCase" accessors="true" { // Load Module CF Mappings so modules can work properly variables.controller.getModuleService().loadMappings(); // Auto registration of test as interceptor - variables.controller - .getInterceptorService() - .registerInterceptor( interceptorObject = this ); + variables.controller.getInterceptorService().registerInterceptor( interceptorObject = this ); + // Do we need to autowire this test? + if( variables.autowire ){ + variables.controller.getWireBox().autowire( + target : this, + targetId : variables.metadata.path + ); + } } + + // Let's add Custom Matchers + addMatchers( "coldbox.system.testing.CustomMatchers" ); } /** @@ -132,7 +155,7 @@ component extends="testbox.system.compat.framework.TestCase" accessors="true" { variables.controller = application[ getColdBoxAppKey() ]; } // remove context + reset headers - getController().getRequestService().removeContext(); + variables.controller.getRequestService().removeContext(); getPageContextResponse().reset(); request._lastInvalidEvent = ""; } @@ -143,7 +166,7 @@ component extends="testbox.system.compat.framework.TestCase" accessors="true" { */ function afterTests(){ if ( this.unLoadColdbox ) { - structDelete( application, getColdboxAppKey() ); + shutdownColdBox(); } } @@ -167,14 +190,44 @@ component extends="testbox.system.compat.framework.TestCase" accessors="true" { } } + /** + * Gracefully shutdown ColdBox + */ + function shutdownColdBox(){ + // Graceful shutdown + if ( structKeyExists( application, getColdboxAppKey() ) ) { + application[ getColdboxAppKey() ].getLoaderService().processShutdown(); + } + + // Wipe app scopes + structDelete( application, getColdboxAppKey() ); + structDelete( application, "wirebox" ); + } + /** * Reset the persistence of the unit test coldbox app, basically removes the controller from application scope + * + * @orm Reload ORM or not + * @wipeRequest Wipe the request scope + * * @return BaseTestCase */ - function reset( boolean clearMethods = false, decorator ){ - structDelete( application, getColdboxAppKey() ); + function reset( boolean orm = false, boolean wipeRequest = true ){ + // Shutdown gracefully ColdBox + shutdownColdBox(); + + // Lucee Cleanups + if ( server.keyExists( "lucee" ) ) { + pagePoolClear(); + } + + // ORM + if ( arguments.orm ) { + ormReload(); + } - if ( !structIsEmpty( request ) ) { + // Wipe out request scope. + if ( arguments.wipeRequest && !structIsEmpty( request ) ) { lock type="exclusive" scope="request" timeout=10 { if ( !structIsEmpty( request ) ) { structClear( request ); @@ -192,9 +245,7 @@ component extends="testbox.system.compat.framework.TestCase" accessors="true" { * @return coldbox.system.testing.mock.web.MockController */ function getMockController(){ - return prepareMock( - new coldbox.system.testing.mock.web.MockController( "/unittest", "unitTest" ) - ); + return prepareMock( new coldbox.system.testing.mock.web.MockController( "/unittest", "unitTest" ) ); } /** @@ -218,10 +269,10 @@ component extends="testbox.system.compat.framework.TestCase" accessors="true" { // Create functioning request context mockRC = getMockBox().createMock( "coldbox.system.web.context.RequestContext" ); - mockController = createObject( - "component", - "coldbox.system.testing.mock.web.MockController" - ).init( "/unittest", "unitTest" ); + mockController = createObject( "component", "coldbox.system.testing.mock.web.MockController" ).init( + "/unittest", + "unitTest" + ); // Create mock properties rcProps.DefaultLayout = ""; @@ -253,10 +304,7 @@ component extends="testbox.system.compat.framework.TestCase" accessors="true" { var mockLocation = getController().getWireBox().locateInstance( arguments.name ); if ( len( mockLocation ) ) { - return getMockBox().createMock( - className = mockLocation, - clearMethods = arguments.clearMethods - ); + return getMockBox().createMock( className = mockLocation, clearMethods = arguments.clearMethods ); } else { throw( message = "Model object #arguments.name# could not be located.", @@ -331,7 +379,8 @@ component extends="testbox.system.compat.framework.TestCase" accessors="true" { */ function setupRequest( required event ){ // Setup the incoming event - URL[ getController().getSetting( "EventName" ) ] = arguments.event; + URL[ getController().getSetting( "EventName" ) ] = arguments.event; + FORM[ getController().getSetting( "EventName" ) ] = arguments.event; // Cleanup for invalid event handlers structDelete( request, "_lastInvalidEvent" ); // Capture the request @@ -519,7 +568,7 @@ component extends="testbox.system.compat.framework.TestCase" accessors="true" { // In either case, if the interceptor doesn't exists, just ignore it. } catch ( any e1 ) { // Are we doing exception handling? - if ( withExceptionHandling ) { + if ( arguments.withExceptionHandling ) { try { processException( cbController, e1 ); } catch ( any e2 ) { @@ -772,14 +821,13 @@ component extends="testbox.system.compat.framework.TestCase" accessors="true" { /** * Get an interceptor reference + * * @interceptorName The name of the interceptor to retrieve * * @return Interceptor */ function getInterceptor( required interceptorName ){ - return getController() - .getInterceptorService() - .getInterceptor( argumentCollection = arguments ); + return getController().getInterceptorService().getInterceptor( argumentCollection = arguments ); } /** @@ -845,9 +893,7 @@ component extends="testbox.system.compat.framework.TestCase" accessors="true" { queryString .listToArray( "&" ) .each( function( item ){ - queryParams[ urlDecode( item.getToken( 1, "=" ) ) ] = urlDecode( - item.getToken( 2, "=" ) - ); + queryParams[ urlDecode( item.getToken( 1, "=" ) ) ] = urlDecode( item.getToken( 2, "=" ) ); } ); return queryParams; @@ -867,9 +913,7 @@ component extends="testbox.system.compat.framework.TestCase" accessors="true" { var prc = event.getPrivateCollection(); // Announce interception - arguments.controller - .getInterceptorService() - .announce( "onException", { exception : arguments.exception } ); + arguments.controller.getInterceptorService().announce( "onException", { exception : arguments.exception } ); // Store exception in private context event.setPrivateValue( "exception", oException ); @@ -880,9 +924,7 @@ component extends="testbox.system.compat.framework.TestCase" accessors="true" { // Run custom Exception handler if Found, else run default exception routines if ( len( arguments.controller.getSetting( "ExceptionHandler" ) ) ) { try { - arguments.controller.runEvent( - arguments.controller.getSetting( "Exceptionhandler" ) - ); + arguments.controller.runEvent( arguments.controller.getSetting( "Exceptionhandler" ) ); } catch ( Any e ) { // Log Original Error First appLogger.error( diff --git a/system/testing/CustomMatchers.cfc b/system/testing/CustomMatchers.cfc new file mode 100644 index 000000000..58976938f --- /dev/null +++ b/system/testing/CustomMatchers.cfc @@ -0,0 +1,108 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * All the custom matchers ColdBox registers within TestBox for easier testing! + */ +component{ + + /** + * Checks if the ColdBox response obejct has a matched status code + *
+	 * expect( event.getResponse() ).toHaveStatus( statusCode );
+	 * 
+ */ + function toHaveStatus( expectation, args = {} ){ + // handle both positional and named arguments + param args.statusCode = ""; + if ( structKeyExists( args, 1 ) ) { + args.statusCode = args[ 1 ]; + } + param args.message = ""; + if ( structKeyExists( args, 2 ) ) { + args.message = args[ 2 ]; + } + if ( !len( args.statusCode ) ) { + expectation.message = "No status code provided."; + return false; + } + var statusCode = expectation.actual.getStatusCode(); + if ( statusCode != args.statusCode ) { + expectation.message = "#args.message#. Received incorrect status code. Expected [#args.statusCode#]. Received [#statusCode#]."; + debug( expectation.actual.getMemento() ); + return false; + } + return true; + } + + /** + * Expectation for testing again cbValidation invalid data fields returned in a Restful response + * by looking into the response object + * + *
+	 * expect( event.getResponse() ).toHaveInvalidData( "username", "is required" )
+	 * expect( event.getResponse() ).toHaveInvalidData( "role", "is required" )
+	 * expect( event.getResponse() ).toHaveInvalidData( "permission", "is not unique" )
+	 * expect( event.getResponse() ).toHaveInvalidData( "status", "does not match" )
+	 * 
+ */ + function toHaveInvalidData( expectation, args = {} ){ + param args.field = ""; + if ( structKeyExists( args, 1 ) ) { + args.field = args[ 1 ]; + } + param args.error = ""; + if ( structKeyExists( args, 2 ) ) { + args.error = args[ 2 ]; + } + param args.message = ""; + if ( structKeyExists( args, 3 ) ) { + args.message = args[ 3 ]; + } + + // If !400 then there is no invalid data + if ( expectation.actual.getStatusCode() != 400 ) { + expectation.message = "#args.message#. Received incorrect status code. Expected [400]. Received [#expectation.actual.getStatusCode()#]."; + debug( expectation.actual.getMemento() ); + return false; + } + // If no field passed, we just check that invalid data was found + if ( !len( args.field ) ) { + if ( expectation.actual.getData().isEmpty() ) { + expectation.message = "#args.message#. Received incorrect status code. Expected [400]. Received [#expectation.actual.getStatusCode()#]."; + debug( expectation.actual.getMemento() ); + return false; + } + return true; + } + // We have a field to check and it has data + if ( + !structKeyExists( + expectation.actual.getData(), + args.field + ) || expectation.actual.getData()[ args.field ].isEmpty() + ) { + expectation.message = "#args.message#. The requested field [#args.field#] does not have any invalid data."; + debug( expectation.actual.getMemento() ); + return false; + } + // Do we have any error messages to check? + if ( len( args.error ) ) { + try { + expect( + expectation.actual.getData()[ args.field ] + .map( function( item ){ + return item.message; + } ) + .toList() + ).toInclude( args.error ); + } catch ( any e ) { + debug( expectation.actual.getMemento() ); + rethrow; + } + } + + // We checked and it's all good! + return true; + } +} \ No newline at end of file diff --git a/system/testing/mock/web/MockController.cfc b/system/testing/mock/web/MockController.cfc index 107ea52d6..e3ddbcd2f 100755 --- a/system/testing/mock/web/MockController.cfc +++ b/system/testing/mock/web/MockController.cfc @@ -6,6 +6,9 @@ */ component extends="coldbox.system.web.Controller" accessors="true" { + // Easy public marker for easy recognition of the mock controller. + this.mockController = true; + /** * Constructor */ diff --git a/system/web/Renderer.cfc b/system/web/Renderer.cfc index cf92f3d76..cf8fa4fa3 100755 --- a/system/web/Renderer.cfc +++ b/system/web/Renderer.cfc @@ -46,11 +46,12 @@ component /** * Constructor + * * @controller The ColdBox main controller * @controller.inject coldbox */ function init( required controller ){ - // setup controller + // Register the Controller variables.controller = arguments.controller; // Register LogBox variables.logBox = arguments.controller.getLogBox(); @@ -64,14 +65,17 @@ component variables.wireBox = arguments.controller.getWireBox(); // Set Conventions, Settings and Properties - variables.layoutsConvention = variables.controller.getColdBoxSetting( "layoutsConvention" ); - variables.viewsConvention = variables.controller.getColdBoxSetting( "viewsConvention" ); - variables.appMapping = variables.controller.getSetting( "AppMapping" ); - variables.viewsExternalLocation = variables.controller.getSetting( "ViewsExternalLocation" ); - variables.layoutsExternalLocation = variables.controller.getSetting( "LayoutsExternalLocation" ); - variables.modulesConfig = variables.controller.getSetting( "modules" ); - variables.viewsHelper = variables.controller.getSetting( "viewsHelper" ); - variables.viewCaching = variables.controller.getSetting( "viewCaching" ); + variables.layoutsConvention = variables.controller.getColdBoxSetting( "layoutsConvention" ); + variables.viewsConvention = variables.controller.getColdBoxSetting( "viewsConvention" ); + variables.appMapping = variables.controller.getSetting( "AppMapping" ); + variables.viewsExternalLocation = variables.controller.getSetting( "ViewsExternalLocation" ); + variables.layoutsExternalLocation = variables.controller.getSetting( "LayoutsExternalLocation" ); + variables.modulesConfig = variables.controller.getSetting( "modules" ); + variables.viewsHelper = variables.controller.getSetting( "viewsHelper" ); + variables.viewCaching = variables.controller.getSetting( "viewCaching" ); + // Layouts + Views Reference Maps + variables.layoutsRefMap = {}; + variables.viewsRefMap = {}; // Verify View Helper Template extension + location if ( len( variables.viewsHelper ) ) { @@ -619,10 +623,12 @@ component // Check cached paths first if ( - structKeyExists( controller.getSetting( "layoutsRefMap" ), cbox_layoutLocationKey ) AND variables.isDiscoveryCaching + structKeyExists( variables.layoutsRefMap, cbox_layoutLocationKey ) + AND + variables.isDiscoveryCaching ) { lock name="#cbox_layoutLocationKey#.#lockName#" type="readonly" timeout="15" throwontimeout="true" { - cbox_layoutLocation = structFind( controller.getSetting( "layoutsRefMap" ), cbox_layoutLocationKey ); + cbox_layoutLocation = structFind( variables.layoutsRefMap, cbox_layoutLocationKey ); } } else { lock name="#cbox_layoutLocationKey#.#lockname#" type="exclusive" timeout="15" throwontimeout="true" { @@ -632,7 +638,7 @@ component explicitModule = cbox_explicitModule ); structInsert( - controller.getSetting( "layoutsRefMap" ), + variables.layoutsRefMap, cbox_layoutLocationKey, cbox_layoutLocation, true @@ -849,12 +855,11 @@ component var locationKey = arguments.view & arguments.module & arguments.explicitModule; var locationUDF = variables.locateView; var refMap = { viewPath : "", viewHelperPath : [] }; - var viewsRefMap = controller.getSetting( "viewsRefMap" ); // Check cached paths first ---> lock name="#locationKey#.#lockName#" type="readonly" timeout="15" throwontimeout="true" { - if ( structKeyExists( viewsRefMap, locationKey ) AND variables.isDiscoveryCaching ) { - return structFind( viewsRefMap, locationKey ); + if ( structKeyExists( variables.viewsRefMap, locationKey ) AND variables.isDiscoveryCaching ) { + return structFind( variables.viewsRefMap, locationKey ); } } @@ -895,10 +900,10 @@ component } // Lock and create view entry - if ( NOT structKeyExists( viewsRefMap, locationKey ) ) { + if ( NOT structKeyExists( variables.viewsRefMap, locationKey ) ) { lock name="#locationKey#.#lockName#" type="exclusive" timeout="15" throwontimeout="true" { structInsert( - viewsRefMap, + variables.viewsRefMap, locationKey, refMap, true diff --git a/system/web/RendererEncapsulator.cfm b/system/web/RendererEncapsulator.cfm index ef09ed284..00b31d605 100644 --- a/system/web/RendererEncapsulator.cfm +++ b/system/web/RendererEncapsulator.cfm @@ -12,12 +12,12 @@ variables.renderedHelpers = {}; // Merge variables from renderer - variables.append( - attributes.rendererVariables.filter( function( key, value ){ - return !listFindNoCase( "local,attributes,arguments", arguments.key ); - } ), - true - ); + // Moved to simple for/loop to avoid closure memory issues and slowdowns + for( myVar in attributes.rendererVariables ) { + if( !listFindNoCase( "local,attributes,arguments", myVar ) ) { + variables[ myVar ] = attributes.rendererVariables[ myVar ]; + } + } // Localize context variables.event = attributes.event; diff --git a/system/web/config/ApplicationLoader.cfc b/system/web/config/ApplicationLoader.cfc index 3e1a6ad2d..288ba443e 100644 --- a/system/web/config/ApplicationLoader.cfc +++ b/system/web/config/ApplicationLoader.cfc @@ -233,10 +233,6 @@ component accessors="true" { // Incorporate their config.cfc settings and override structAppend( configStruct, coldboxSettings, true ); - // Common Structures for layouts and views - configStruct[ "layoutsRefMap" ] = {}; - configStruct[ "viewsRefMap" ] = {}; - /* ::::::::::::::::::::::::::::::::::::::::: COLDBOX SETTINGS :::::::::::::::::::::::::::::::::::::::::::: */ // Check the defaultEvent, if no length, default it @@ -793,9 +789,10 @@ component accessors="true" { for ( var key in environments ) { // loop over patterns for ( var i = 1; i lte listLen( environments[ key ] ); i = i + 1 ) { - if ( reFindNoCase( listGetAt( environments[ key ], i ), CGI.SERVER_NAME ) ) { + if ( reFindNoCase( listGetAt( environments[ key ], i ), CGI.HTTP_HOST ) ) { // set new environment configStruct.environment = key; + break; } } } diff --git a/system/web/context/ExceptionBean.cfc b/system/web/context/ExceptionBean.cfc index ad64e6703..fa96d64c4 100644 --- a/system/web/context/ExceptionBean.cfc +++ b/system/web/context/ExceptionBean.cfc @@ -348,63 +348,22 @@ component accessors="true" { * @return The nicer trace */ function processStackTrace( required str ){ + // cfformat-ignore-start + // Not using encodeForHTML() as it is too destructive and ruins whitespace chars and other stuff - arguments.str = htmlEditFormat( arguments.str ); - - var aMatches = reMatchNoCase( "\(([^\)]+)\)", arguments.str ); - for ( var aString in aMatches ) { - arguments.str = replaceNoCase( - arguments.str, - aString, - "#aString#", - "all" - ); - } - var aMatches = reMatchNoCase( "\[([^\]]+)\]", arguments.str ); - for ( var aString in aMatches ) { - arguments.str = replaceNoCase( - arguments.str, - aString, - "#aString#", - "all" - ); - } - var aMatches = reMatchNoCase( - "\$([^(\(|\:)]+)(\:|\()", - arguments.str - ); - for ( var aString in aMatches ) { - arguments.str = replaceNoCase( - arguments.str, - aString, - "#aString#", - "all" - ); - } - arguments.str = replace( - arguments.str, - chr( 13 ) & chr( 10 ), - chr( 13 ), - "all" - ); - arguments.str = replace( - arguments.str, - chr( 10 ), - chr( 13 ), - "all" - ); - arguments.str = replace( - arguments.str, - chr( 13 ), - "
", - "all" - ); - arguments.str = replaceNoCase( - arguments.str, - chr( 9 ), - repeatString( " ", 4 ), - "all" - ); + arguments.str = HTMLEditFormat( arguments.str ); + // process functions e.g. $funcINDEX.runFunction( + arguments.str = reReplaceNoCase( arguments.str, "\$([^(\(|\:)]+)(\:|\()", "$\1(", "ALL" ); + // process characters within parentheses e.g. (ServletAuthenticationCallHandler.java:57) + arguments.str = reReplaceNoCase( arguments.str, "\(([^\)]+)\)", "(\1)", "ALL" ); + // process characters in brackets e.g. [hello world] + arguments.str = reReplaceNoCase( arguments.str, "\[([^\]]+)\]", "[\1]", "ALL" ); + arguments.str = replace( arguments.str, chr( 13 ) & chr( 10 ), chr( 13 ) , 'all' ); + arguments.str = replace( arguments.str, chr( 10 ), chr( 13 ) , 'all' ); + arguments.str = replace( arguments.str, chr( 13 ), '
' , 'all' ); + arguments.str = replaceNoCase( arguments.str, chr(9), repeatString( " ", 4 ), "all" ); + + // cfformat-ignore-end return arguments.str; } @@ -419,7 +378,8 @@ component accessors="true" { str = replace( str, arguments.item, - "#arguments.item#", + "#arguments.item# +", "all" ); } ); @@ -448,7 +408,9 @@ component accessors="true" { * @return The HTML of the scope data */ function displayScope( required scope ){ - var list = createObject( "java", "java.lang.StringBuilder" ).init( "
Bug Date:#dateformat(now(), "MM/DD/YYYY")# #timeformat(now(),"hh:MM:SS TT")##dateformat(now(), "mm/dd/yyyy")# #timeformat(now(),"hh:mm:ss tt")#
Host & Server: #encodeForHTML( CGI.SERVER_NAME )# #encodeForHTML( local.thisInetHost )##encodeForHTML( CGI.HTTP_HOST )# #encodeForHTML( local.thisInetHost )#
Query String:
" ); + var list = createObject( "java", "java.lang.StringBuilder" ).init( "
+ + " ); var orderedArr = arguments.scope; if ( structKeyExists( arguments.scope, "itemorder" ) ) { @@ -456,48 +418,78 @@ component accessors="true" { } for ( var i in orderedArr ) { - list.append( "" ); + list.append( " + " ); // Null Checks if ( !structKeyExists( arguments.scope , i ) || isNull( arguments.scope[ i ] ) ) { - arguments.scope[ i ] = "Java Null"; + arguments.scope[ i ] = " + Java Null + + "; } if ( isDate( arguments.scope[ i ] ) ) { - list.append( "" ); - list.append( " + " ); + list.append( "" ); + timeFormat( arguments.scope[ i ], "HH:mm:ss" ) & " + " ); } else if ( isSimpleValue( arguments.scope[ i ] ) ) { - list.append( "" ); - list.append( "" ); + list.append( " + " ); + list.append( " + " ); } else if( isObject( arguments.scope[ i ] ) ) { - list.append( "" ); - list.append( "" ); + list.append( " + " ); + list.append( " + " ); } else { savecontent variable="local.myContent" { - writeDump( - var = arguments.scope[ i ], - format = "html", - top = 2, - expand = false - ) + try{ + writeDump( + var = arguments.scope[ i ], + format = "html", + top = 2, + expand = false + ) + } catch( any e ){ + writeDump( + var = arguments.scope[ i ].toString(), + format = "html", + top = 2, + expand = false + ) + } } - list.append( "" ); - list.append( "" ); + list.append( " + " ); + list.append( " + " ); } - list.append( "" ); + list.append( " + + " ); } if ( !structCount( arguments.scope ) ) { list.append( " - - " ); + + + " ); } - list.append( "
" & i & "" & + list.append( "" & i & "" & dateFormat( arguments.scope[ i ], "mm/dd/yyyy" ) & " " & - timeFormat( arguments.scope[ i ], "HH:mm:ss" ) & "" & i & "" & ( len( arguments.scope[ i ] ) ? arguments.scope[ i ] : "---" ) & "" & i & " + " & ( len( arguments.scope[ i ] ) ? arguments.scope[ i ] : "--- + " ) & " + " & i & " [" & getMetaData( arguments.scope[ i ] ).name & "] Instance" & i & "[" & getMetaData( arguments.scope[ i ] ).name & "] Instance" & i & "" & local.myContent & "" & i & "" & local.myContent & "
- No details found! -
+ No details found! +
" ); + list.append( " + + +" ); return list.toString(); } diff --git a/system/web/context/RequestContext.cfc b/system/web/context/RequestContext.cfc index 5fa75dcd4..d75884077 100644 --- a/system/web/context/RequestContext.cfc +++ b/system/web/context/RequestContext.cfc @@ -1157,16 +1157,29 @@ component serializable="false" accessors="true" { } /** - * Returns the full url including the protocol, host, and path. + * Returns the full url including the protocol, host, mapping, path info, and query string. * Handles SES urls gracefully. */ string function getFullURL(){ return arrayToList( [ - isSSL() ? "https://" : "http://", - CGI.SERVER_NAME, - listFind( "80,443", CGI.SERVER_PORT ) ? "" : ":" & CGI.SERVER_PORT, - isSES() ? "" : "/index.cfm", + getSesBaseUrl(), + CGI.PATH_INFO, + CGI.QUERY_STRING != "" && CGI.PATH_INFO == "" ? "/" : "", + CGI.QUERY_STRING != "" ? "?" : "", + CGI.QUERY_STRING + ], + "" + ); + } + + /** + * Returns the full relative path to the requested event: does not include protocol and host + */ + string function getFullPath(){ + return arrayToList( + [ + variables.controller.getRoutingService().getRouter().composeRoutingPath(), CGI.PATH_INFO, CGI.QUERY_STRING != "" && CGI.PATH_INFO == "" ? "/" : "", CGI.QUERY_STRING != "" ? "?" : "", @@ -1255,7 +1268,7 @@ component serializable="false" accessors="true" { */ string function buildLink( to, - queryString = "", + queryString = "", boolean translate = true, boolean ssl, baseURL = "" @@ -1728,14 +1741,17 @@ component serializable="false" accessors="true" { boolean json = false, boolean xml = false ){ - var content = getHTTPRequestData().content; - - // ToString() neccessary when body comes in as binary. - if ( arguments.json and isJSON( toString( content ) ) ) return deserializeJSON( toString( content ) ); - if ( arguments.xml and len( toString( content ) ) and isXML( toString( content ) ) ) - return xmlParse( toString( content ) ); + // Only read the content once + if ( !StructKeyExists( variables.privateContext, "_httpContent" ) ) { + variables.privateContext._httpContent = getHTTPRequestData().content; + if ( arguments.json and isJSON( toString( variables.privateContext._httpContent ) ) ) { + variables.privateContext._httpContent = deserializeJSON( toString( variables.privateContext._httpContent ) ); + } else if ( arguments.xml and len( toString( variables.privateContext._httpContent ) ) and isXML( toString( variables.privateContext._httpContent ) ) ) { + variables.privateContext._httpContent = xmlParse( toString( variables.privateContext._httpContent ) ); + } + } - return content; + return variables.privateContext._httpContent; } /** @@ -1745,7 +1761,7 @@ component serializable="false" accessors="true" { * @defaultValue The default value, if not found */ function getHTTPHeader( required header, defaultValue = "" ){ - var headers = getHTTPRequestData().headers; + var headers = getHTTPRequestData( false ).headers; // ADOBE FIX YOUR ISNULL BS if ( headers.keyExists( arguments.header ) ) { diff --git a/system/web/context/Response.cfc b/system/web/context/Response.cfc index 25ae0d758..daa277f54 100644 --- a/system/web/context/Response.cfc +++ b/system/web/context/Response.cfc @@ -321,12 +321,13 @@ component accessors="true" { Response function setErrorMessage( required errorMessage, statusCode, - statusText = "" + statusText="" ){ setError( true ); addMessage( arguments.errorMessage ); if ( !isNull( arguments.statusCode ) ) { + setStatus( arguments.statusCode, arguments.statusText diff --git a/system/web/routing/Router.cfc b/system/web/routing/Router.cfc index b0bb3160f..e632a441f 100644 --- a/system/web/routing/Router.cfc +++ b/system/web/routing/Router.cfc @@ -169,22 +169,11 @@ component variables.throwOnInvalidExtension = false; // Initialize the valid extensions to detect variables.validExtensions = variables.VALID_EXTENSIONS; - // Base Routing URL, defaults to the domain and app mapping defined by the routing services - if ( len( controller.getSetting( "RoutingAppMapping" ) ) lte 1 ) { - variables.baseURL = "http://#CGI.SERVER_NAME#"; - if ( CGI.SERVER_PORT != 80 && CGI.SERVER_PORT != 443 ) { - variables.baseURL &= ":#CGI.SERVER_PORT#"; - } - } else { - variables.baseURL = "http://#CGI.SERVER_NAME#"; - if ( CGI.SERVER_PORT != 80 && CGI.SERVER_PORT != 443 ) { - variables.baseURL &= ":#CGI.SERVER_PORT#"; - } - variables.baseURL &= controller.getSetting( "RoutingAppMapping" ); - } + // Initialize the base URL as empty in case the user overrides it in their own router. + variables.baseUrl = ""; // Are full rewrites enabled - variables.fullRewrites = false; - variables.multiDomainDiscovery = true; + variables.fullRewrites = false; + variables.multiDomainDiscovery = true; return this; } @@ -199,29 +188,28 @@ component /** * This method is called by the Routing Services to make sure the router is ready for operation. - * This is ONLY called by the routing services. + * This is ONLY called by the routing services and only ONCE in the Application Life-Cycle */ function startup(){ + // Verify baseUrl is still empty to default it for operation + if ( !len( variables.baseUrl ) ) { + variables.baseUrl = composeRoutingUrl(); + } + // Check if rewrites turned off. If so, append the `index.cfm` to it. if ( !variables.fullRewrites AND !findNoCase( "index.cfm", variables.baseURL ) ) { variables.baseURL &= "/index.cfm"; } - // Remove any double slashes - variables.baseURL = reReplace( variables.baseURL, "\/\/$", "/", "all" ); - // Save the base URL in the application settings - variables.controller.setSetting( "SESBaseURL", variables.baseURL ); - variables.controller.setSetting( - "HTMLBaseURL", - replaceNoCase( variables.baseURL, "index.cfm", "" ) - ); + // Remove any double slashes: sometimes proxies can interfere + variables.baseURL = reReplace( variables.baseURL, "\/\/$", "/", "all" ); - // Configure Context that we are enabled and with the base URL for routing + // Save the base URIs and Paths in the application settings variables.controller - .getRequestService() - .getContext() - .setSESEnabled( variables.enabled ) - .setSESBaseURL( variables.baseURL ); + .setSetting( "SESBaseURL", variables.baseURL ) + .setSetting( "SESBasePath", composeRoutingPath() ) + .setSetting( "HTMLBaseURL", replaceNoCase( variables.baseURL, "index.cfm", "" ) ) + .setSetting( "HTMLBasePath", replaceNoCase( composeRoutingPath(), "index.cfm", "" ) ); } /** @@ -929,10 +917,7 @@ component // Check Digits Repetions if ( find( "{", thisPattern ) ) { thisRegex = listFirst( thisRegex, "{" ) & "{#listLast( thisPattern, "{" )#)"; - arrayAppend( - thisRoute.patternParams, - replace( listFirst( thisPattern, "{" ), ":", "" ) - ); + arrayAppend( thisRoute.patternParams, replace( listFirst( thisPattern, "{" ), ":", "" ) ); } else { thisRegex = thisRegex & "+?)"; arrayAppend( thisRoute.patternParams, thisPatternParam ); @@ -1033,10 +1018,7 @@ component // Check Digits Repetions if ( find( "{", thisDomain ) ) { thisRegex = listFirst( thisRegex, "{" ) & "{#listLast( thisDomain, "{" )#)"; - arrayAppend( - thisRoute.domainParams, - replace( listFirst( thisDomain, "{" ), ":", "" ) - ); + arrayAppend( thisRoute.domainParams, replace( listFirst( thisDomain, "{" ), ":", "" ) ); } else { thisRegex = thisRegex & "+?)"; arrayAppend( thisRoute.domainParams, thisDomainParam ); @@ -1086,10 +1068,7 @@ component } // end looping of pattern optionals if ( right( thisRoute.regexDomain, 1 ) == "." ) { - thisRoute.regexDomain = left( - thisRoute.regexDomain, - len( thisRoute.regexDomain ) - 1 - ); + thisRoute.regexDomain = left( thisRoute.regexDomain, len( thisRoute.regexDomain ) - 1 ); } } @@ -1206,10 +1185,8 @@ component args = { pattern : arguments.pattern, event : arguments.target, - verbs : ( - variables.thisRoute.keyExists( "verbs" ) ? variables.thisRoute.verbs : "" - ), - name : arguments.name + verbs : ( variables.thisRoute.keyExists( "verbs" ) ? variables.thisRoute.verbs : "" ), + name : arguments.name }; } // Closure/Lambda => Response @@ -1217,10 +1194,8 @@ component args = { pattern : arguments.pattern, response : arguments.target, - verbs : ( - variables.thisRoute.keyExists( "verbs" ) ? variables.thisRoute.verbs : "" - ), - name : arguments.name + verbs : ( variables.thisRoute.keyExists( "verbs" ) ? variables.thisRoute.verbs : "" ), + name : arguments.name }; } @@ -1898,15 +1873,8 @@ component statusText = "Ok" ){ // Arg Check - if ( - !isClosure( arguments.body ) && !isCustomFunction( arguments.body ) && !isSimpleValue( - arguments.body - ) - ) { - throw( - type : "InvalidArgumentException", - message: "The 'body' argument is not of type closure or string" - ); + if ( !isClosure( arguments.body ) && !isCustomFunction( arguments.body ) && !isSimpleValue( arguments.body ) ) { + throw( type: "InvalidArgumentException", message: "The 'body' argument is not of type closure or string" ); } // process a with closure if not empty if ( !variables.withClosure.isEmpty() ) { @@ -1971,6 +1939,33 @@ component return this; } + /** + * Composes the base URL for the server using the following composition: + * - protocol + * - host + port + * - routing app mapping + * - full Url routing or front controller routing + */ + string function composeRoutingUrl(){ + return ( + // Protocol + variables.controller + .getRequestService() + .getContext() + .isSSL() ? "https://" : "http://" + ) & + CGI.HTTP_HOST & // multi-host + composeRoutingPath(); // Routing Path + } + + /** + * Composes the base routing path with no host or protocol + */ + string function composeRoutingPath(){ + return variables.controller.getSetting( "RoutingAppMapping" ) & // routing app mapping + ( variables.fullRewrites ? "" : "index.cfm" ); // full or controller routing + } + /*****************************************************************************************/ /************************************ PRIVATE ********************************************/ /*****************************************************************************************/ @@ -1988,11 +1983,7 @@ component ){ var actionSet = arguments.initial; - if ( - structKeyExists( arguments, "only" ) && !isNull( arguments.only ) && !arrayIsEmpty( - arguments.only - ) - ) { + if ( structKeyExists( arguments, "only" ) && !isNull( arguments.only ) && !arrayIsEmpty( arguments.only ) ) { actionSet = {}; for ( var HTTPVerb in arguments.initial ) { var methodName = arguments.initial[ HTTPVerb ]; @@ -2004,11 +1995,7 @@ component } } - if ( - structKeyExists( arguments, "except" ) && !isNull( arguments.except ) && !arrayIsEmpty( - arguments.except - ) - ) { + if ( structKeyExists( arguments, "except" ) && !isNull( arguments.except ) && !arrayIsEmpty( arguments.except ) ) { for ( var HTTPVerb in arguments.initial ) { var methodName = arguments.initial[ HTTPVerb ]; for ( var exceptAction in arguments.except ) { diff --git a/system/web/services/HandlerService.cfc b/system/web/services/HandlerService.cfc index 91431e991..01f7a3730 100644 --- a/system/web/services/HandlerService.cfc +++ b/system/web/services/HandlerService.cfc @@ -136,6 +136,8 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { * * @ehBean The event handler bean representation * @requestContext The request context object + * + * @return The event handler object represented by the ehBean */ function getHandler( required ehBean, required requestContext ){ var oRequestContext = arguments.requestContext; @@ -164,12 +166,12 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { return oEventHandler; } - // Invalid Event procedures - invalidEvent( arguments.ehBean.getFullEvent(), arguments.ehBean ); + // The handler exists but the action requested does not, let's go into invalid execution mode + var targetInvalidEvent = invalidEvent( arguments.ehBean.getFullEvent(), arguments.ehBean ); - // If we get here, then the invalid event kicked in and exists, else an exception is thrown + // If we get here, then the invalid event kicked in and exists, else an exception is thrown above // Go retrieve the handler that will handle the invalid event so it can execute. - return getHandler( getHandlerBean( arguments.ehBean.getFullEvent() ), oRequestContext ); + return getHandler( getHandlerBean( targetInvalidEvent ), oRequestContext ); } // method check finalized. @@ -324,11 +326,11 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { } // Run invalid event procedures, handler not found as a module or in all lists - invalidEvent( arguments.event, oHandlerBean ); + arguments.event = invalidEvent( arguments.event, oHandlerBean ); // If we get here, then invalid event handler is active and we need to // return an event handler bean that matches it - return getHandlerBean( oHandlerBean.getFullEvent() ); + return getHandlerBean( arguments.event ); } /** @@ -415,16 +417,19 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { } /** - * Invalid Event procedures + * Invalid Event procedures. An invalid event is detected, so this method + * will verify if the application has an invalidEventHandler or an interceptor + * listening to `onInvalidEvent` modifies the handler bean. Then this method will + * either return the invalid event handler event, or set an exception to be captured. * * @event The event that was found to be invalid - * @ehBean The event handler bean + * @ehBean The event handler bean representing the invalid event * * @throws EventHandlerNotRegisteredException,InvalidEventHandlerException * - * @return HandlerService + * @return The string event that should be executed as the invalid event handler or throws an EventHandlerNotRegisteredException */ - function invalidEvent( required string event, required ehBean ){ + string function invalidEvent( required string event, required ehBean ){ // Announce it var iData = { "invalidEvent" : arguments.event, @@ -435,24 +440,30 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { // If the override was changed by the interceptors then they updated the ehBean of execution if ( iData.override ) { - return this; + return ehBean.getFullEvent(); } // Param our last invalid event just incase param request._lastInvalidEvent = ""; - // If we got here, we have an invalid event and no override, throw a 404 header - controller - .getRequestService() - .getContext() - .setHTTPHeader( statusCode = 404, statusText = "Not Found" ); - // If invalidEventHandler is registered, use it if ( len( variables.invalidEventHandler ) ) { // Test for invalid Event Error as well so we don't go in an endless error loop - if ( compareNoCase( arguments.event, request._lastInvalidEvent ) eq 0 ) { + if ( + compareNoCase( arguments.event, request._lastInvalidEvent ) eq 0 + && + !structKeyExists( controller, "mockController" ) // Verify this is a real and not a mock controller. + ) { + var exceptionMessage = "The invalidEventHandler event (#variables.invalidEventHandler#) is also invalid: #arguments.event#"; + // Extra Debugging for illusive CI/Tests exceptions: Remove at one point if discovered. + variables.log.error( exceptionMessage, { + event : arguments.event, + registeredHandlers : variables.registeredHandlers, + fullEvent : ehBean.getFullEvent() + } ); + // Now throw the exception throw( - message : "The invalidEventHandler event is also invalid: #variables.invalidEventHandler#", + message : exceptionMessage, type : "HandlerService.InvalidEventHandlerException" ); } @@ -467,25 +478,15 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { .getContext() .setPrivateValue( "invalidevent", arguments.event ); - // Override Event With On Invalid Event - arguments.ehBean - .setHandler( - reReplace( - variables.invalidEventHandler, - "\.[^.]*$", - "" - ) - ) - .setMethod( listLast( variables.invalidEventHandler, "." ) ) - .setModule( "" ); - - // If module found in invalid event, set it for discovery - if ( find( ":", variables.invalidEventHandler ) ) { - arguments.ehBean.setModule( getToken( variables.invalidEventHandler, 1 ) ); - } + // Override Event With On invalid handler event + return variables.invalidEventHandler; + } // end invalidEventHandler found - return this; - } + // If we got here, we have an invalid event and no override, throw a 404 ERROR + controller + .getRequestService() + .getContext() + .setHTTPHeader( statusCode = 404, statusText = "Not Found" ); // Invalid Event Detected, log it in the Application log, not a coldbox log but an app log variables.log.error( diff --git a/system/web/services/LoaderService.cfc b/system/web/services/LoaderService.cfc index fd00318cd..ec79a30c5 100644 --- a/system/web/services/LoaderService.cfc +++ b/system/web/services/LoaderService.cfc @@ -267,6 +267,10 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { variables.log.info( "† Shutting down ColdBox Task Scheduler..." ); asyncManager.shutdownAllExecutors( force = true ); + // Shutdown LogBox LAST + variables.log.info( "† Shutting down LogBox..." ); + variables.controller.getLogBox().shutdown(); + return this; } diff --git a/system/web/services/RoutingService.cfc b/system/web/services/RoutingService.cfc index 2a706fbe0..50c0258a2 100644 --- a/system/web/services/RoutingService.cfc +++ b/system/web/services/RoutingService.cfc @@ -27,25 +27,20 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { * Once configuration loads prepare for operation */ function onConfigurationLoad(){ - // Prepare dependencies + // Prepare references for faster access variables.log = variables.controller.getLogBox().getLogger( this ); variables.handlersPath = controller.getSetting( "HandlersPath" ); - variables.handlersExternalLocationPath = controller.getSetting( - "HandlersExternalLocationPath" - ); - variables.modules = controller.getSetting( "Modules" ); - variables.eventName = controller.getSetting( "EventName" ); - variables.defaultEvent = controller.getSetting( "DefaultEvent" ); - variables.requestService = controller.getRequestService(); - variables.wirebox = controller.getWireBox(); + variables.handlersExternalLocationPath = controller.getSetting( "HandlersExternalLocationPath" ); + variables.modules = controller.getSetting( "Modules" ); + variables.eventName = controller.getSetting( "EventName" ); + variables.defaultEvent = controller.getSetting( "DefaultEvent" ); + variables.requestService = controller.getRequestService(); + variables.wirebox = controller.getWireBox(); // Routing AppMapping Determinations variables.appMapping = controller.getSetting( "AppMapping" ); - variables.routingAppMapping = ( - len( controller.getSetting( "AppMapping" ) lte 1 ) ? controller.getSetting( - "AppMapping" - ) & "/" : "" - ); + variables.routingAppMapping = ( len( variables.appMapping ) ? variables.appMapping & "/" : "" ); + // Make sure it's prefixed with / variables.routingAppMapping = left( variables.routingAppMapping, 1 ) == "/" ? variables.routingAppMapping : "/#variables.routingAppMapping#"; // Store routing appmapping @@ -82,7 +77,7 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { */ private function loadRouter(){ // Declare types of routers to discover - var legacyRouter = "config/Routes.cfm"; + var legacyRouter = "config/Routes.cfm"; // TODO: Decpreated, remove by ColdBox 7 var modernRouter = "config.Router"; var baseRouter = "coldbox.system.web.routing.Router"; @@ -109,10 +104,8 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { switch ( routerType ) { case "modern": { // Log it - log.info( "Loading Modern Router at: #modernRouter#" ); - var modernRouterPath = ( - variables.appMapping.len() ? "#variables.appMapping#.#modernRouter#" : modernRouter - ); + variables.log.info( "Loading Modern Router at: #modernRouter#" ); + var modernRouterPath = ( variables.appMapping.len() ? "#variables.appMapping#.#modernRouter#" : modernRouter ); // Process as a Router.cfc with virtual inheritance wirebox .registerNewInstance( name = "router@coldbox", instancePath = modernRouterPath ) @@ -123,20 +116,16 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { // Create the Router variables.router = wirebox.getInstance( "router@coldbox" ); // Register the Router as an Interceptor as well. - variables.controller - .getInterceptorService() - .registerInterceptor( interceptorObject = variables.router ); + variables.controller.getInterceptorService().registerInterceptor( interceptorObject = variables.router ); // Process it variables.router.configure(); break; } case "legacy": { // Log it - log.info( "Loading Legacy Router at: #legacyRouter#" ); + variables.log.info( "Loading Legacy Router at: #legacyRouter#" ); // Register basic router - wirebox - .registerNewInstance( name = "router@coldbox", instancePath = baseRouter ) - .setScope( wirebox.getBinder().SCOPES.SINGLETON ); + wirebox.registerNewInstance( name = "router@coldbox", instancePath = baseRouter ).setScope( wirebox.getBinder().SCOPES.SINGLETON ); // Process legacy Routes.cfm. Create a basic Router variables.router = wirebox .getInstance( "router@coldbox" ) @@ -146,14 +135,10 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { } default: { // Log it - log.info( "Loading Base ColdBox Router" ); + variables.log.info( "Loading Base ColdBox Router" ); // Register basic router with default routing - wirebox - .registerNewInstance( name = "router@coldbox", instancePath = baseRouter ) - .setScope( wirebox.getBinder().SCOPES.SINGLETON ); - variables.router = wirebox - .getInstance( "router@coldbox" ) - .addRoute( pattern = "/:handler/:action?" ); + wirebox.registerNewInstance( name = "router@coldbox", instancePath = baseRouter ).setScope( wirebox.getBinder().SCOPES.SINGLETON ); + variables.router = wirebox.getInstance( "router@coldbox" ).addRoute( pattern = "/:handler/:action?" ); } } @@ -180,23 +165,12 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { return; } - // Enable the routing + // Enable the routing services for the request arguments.event.setSESEnabled( true ); - // Activate and record the incoming URL for multi-domain hosting + // Activate and record the incoming URL for multi-domain hosting ONLY if ( variables.router.getMultiDomainDiscovery() ) { - arguments.event.setSESBaseURL( - ( event.isSSL() ? "https://" : "http://" ) & - CGI.SERVER_NAME & - ( - ( - ( event.isSSL() && CGI.SERVER_PORT != 443 ) || - ( !event.isSSL() && CGI.SERVER_PORT != 80 ) - ) ? ":#CGI.SERVER_PORT#" : "" - ) & - variables.routingAppMapping & - ( variables.router.getFullRewrites() ? "" : "index.cfm" ) - ); + arguments.event.setSESBaseURL( variables.router.composeRoutingUrl() ); } // Check for invalid URLs if in strict mode via unique URLs @@ -210,10 +184,7 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { // Extension detection if enabled, so we can do cool extension formats if ( variables.router.getExtensionDetection() ) { - cleanedPaths[ "pathInfo" ] = detectExtension( - cleanedPaths[ "pathInfo" ], - arguments.event - ); + cleanedPaths[ "pathInfo" ] = detectExtension( cleanedPaths[ "pathInfo" ], arguments.event ); } // Find a route to dispatch @@ -310,8 +281,8 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { ) { // Mark as invalid HTTP Exception arguments.event.setIsInvalidHTTPMethod( true ); - if ( log.canDebug() ) { - log.debug( "Invalid HTTP Method detected: #httpMethod#", routeResults.route ); + if ( variables.log.canDebug() ) { + variables.log.debug( "Invalid HTTP Method detected: #httpMethod#", routeResults.route ); } } } @@ -333,8 +304,8 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { ) { // Mark as invalid HTTP Exception arguments.event.setIsInvalidHTTPMethod( true ); - if ( log.canDebug() ) { - log.debug( "Invalid HTTP Method detected: #httpMethod#", routeResults.route ); + if ( variables.log.canDebug() ) { + variables.log.debug( "Invalid HTTP Method detected: #httpMethod#", routeResults.route ); } } @@ -349,20 +320,15 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { if ( structKeyExists( routeResults.route.action, httpMethod ) ) { discoveredEvent &= ".#routeResults.route.action[ httpMethod ]#"; // Send for logging in debug mode - if ( log.canDebug() ) { - log.debug( - "Matched HTTP Method (#HTTPMethod#) to routed action: #routeResults.route.action[ httpMethod ]#" - ); + if ( variables.log.canDebug() ) { + variables.log.debug( "Matched HTTP Method (#HTTPMethod#) to routed action: #routeResults.route.action[ httpMethod ]#" ); } } else { // Mark as invalid HTTP Exception discoveredEvent &= ".onInvalidHTTPMethod"; arguments.event.setIsInvalidHTTPMethod( true ); - if ( log.canDebug() ) { - log.debug( - "Invalid HTTP Method detected: #httpMethod#", - routeResults.route - ); + if ( variables.log.canDebug() ) { + variables.log.debug( "Invalid HTTP Method detected: #httpMethod#", routeResults.route ); } } } @@ -386,10 +352,7 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { // Layout? if ( routeResults.route.layout.len() ) { - arguments.event.setLayout( - name = routeResults.route.layout, - module = routeResults.route.layoutModule - ); + arguments.event.setLayout( name = routeResults.route.layout, module = routeResults.route.layoutModule ); } } @@ -399,11 +362,7 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { } ); // See if Response is dispatched - if ( - isClosure( routeResults.route.response ) || isCustomFunction( - routeResults.route.response - ) || routeResults.route.response.len() - ) { + if ( isClosure( routeResults.route.response ) || isCustomFunction( routeResults.route.response ) || routeResults.route.response.len() ) { renderResponse( routeResults.route, arguments.event ); } @@ -477,24 +436,16 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { if ( ( match.len[ 1 ] IS NOT 0 AND variables.router.getLooseMatching() ) OR - ( - NOT variables.router.getLooseMatching() AND match.len[ 1 ] IS NOT 0 AND match.pos[ - 1 - ] EQ 1 - ) + ( NOT variables.router.getLooseMatching() AND match.len[ 1 ] IS NOT 0 AND match.pos[ 1 ] EQ 1 ) ) { // Verify condition matching if ( - ( - isClosure( _routes[ i ].condition ) || isCustomFunction( - _routes[ i ].condition - ) - ) + ( isClosure( _routes[ i ].condition ) || isCustomFunction( _routes[ i ].condition ) ) AND NOT _routes[ i ].condition( requestString ) ) { // Debug logging - if ( log.canDebug() ) { - log.debug( + if ( variables.log.canDebug() ) { + variables.log.debug( "SES Route matched but condition closure did not pass: #_routes[ i ].toString()# on routed string: #requestString#" ); } @@ -524,10 +475,8 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { } // Debug logging - if ( log.canDebug() ) { - log.debug( - "Route matched: #results.route.toString()# on routed string: #requestString#" - ); + if ( variables.log.canDebug() ) { + variables.log.debug( "Route matched: #results.route.toString()# on routed string: #requestString#" ); } break; @@ -537,8 +486,8 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { // Check if we found a route, else just return empty params struct if ( results.route.isEmpty() ) { - if ( log.canDebug() ) { - log.debug( "No URL routes matched on routed string: #requestString#" ); + if ( variables.log.canDebug() ) { + variables.log.debug( "No URL routes matched on routed string: #requestString#" ); } return results; } @@ -602,8 +551,8 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { // reset pattern matching, if packages found. if ( compare( packagedRequestString, requestString ) NEQ 0 ) { // Log package resolved - if ( log.canDebug() ) { - log.debug( "URL Routing Package Resolved: #packagedRequestString#" ); + if ( variables.log.canDebug() ) { + variables.log.debug( "URL Routing Package Resolved: #packagedRequestString#" ); } // Return found Route recursively. return findRoute( @@ -654,12 +603,7 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { function getCgiElement( required cgiElement, required event ){ // Allow a UDF to manipulate the CGI.PATH_INFO value // in advance of route detection. - if ( - arguments.cgiElement EQ "path_info" AND structKeyExists( - variables.router, - "PathInfoProvider" - ) - ) { + if ( arguments.cgiElement EQ "path_info" AND structKeyExists( variables.router, "PathInfoProvider" ) ) { return variables.router.pathInfoProvider( event = arguments.event ); } return CGI[ arguments.CGIElement ]; @@ -677,11 +621,7 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { var redirectTo = ""; // Determine closure or string relocation string - if ( - isClosure( arguments.routeResults.route.redirect ) || isCustomFunction( - arguments.routeResults.route.redirect - ) - ) { + if ( isClosure( arguments.routeResults.route.redirect ) || isCustomFunction( arguments.routeResults.route.redirect ) ) { redirectTo = routeResults.route.redirect( arguments.routeResults.route, arguments.routeResults.params, @@ -695,16 +635,12 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { if ( redirectTo.findNoCase( "http" ) ) { variables.controller.relocate( URL : redirectTo, - statusCode: ( - arguments.routeResults.route.keyExists( "statusCode" ) ? arguments.routeResults.route.statusCode : 301 - ) + statusCode: ( arguments.routeResults.route.keyExists( "statusCode" ) ? arguments.routeResults.route.statusCode : 301 ) ); } else { variables.controller.relocate( event : redirectTo, - statusCode: ( - arguments.routeResults.route.keyExists( "statusCode" ) ? arguments.routeResults.route.statusCode : 301 - ) + statusCode: ( arguments.routeResults.route.keyExists( "statusCode" ) ? arguments.routeResults.route.statusCode : 301 ) ); } @@ -724,27 +660,19 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { extension = lCase( reReplace( extension, "/$", "", "all" ) ); // check if extension found - if ( - listLen( arguments.requestString, "." ) GT 1 AND len( extension ) AND NOT find( - "/", - extension - ) - ) { + if ( listLen( arguments.requestString, "." ) GT 1 AND len( extension ) AND NOT find( "/", extension ) ) { // Check if extension is valid? if ( variables.router.isValidExtension( extension ) ) { // set the format request collection variable event.setValue( "format", extension ); // debug logging - if ( log.canDebug() ) { - log.debug( "Extension: #extension# detected and set in rc.format" ); + if ( variables.log.canDebug() ) { + variables.log.debug( "Extension: #extension# detected and set in rc.format" ); } // remove it from the string and return string for continued parsing. return left( requestString, len( arguments.requestString ) - extensionLen - 1 ); } else if ( variables.router.getThrowOnInvalidExtension() ) { - event.setHTTPHeader( - statusText = "Invalid Requested Format Extension: #extension#", - statusCode = 406 - ); + event.setHTTPHeader( statusText = "Invalid Requested Format Extension: #extension#", statusCode = 406 ); throw( message = "Invalid requested format extendion: #extension#", detail = "Invalid Request Format Extension Detected: #extension#. Valid extensions are: #variables.router.getValidExtensions()#", @@ -892,11 +820,7 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { if ( directoryExists( root & "/" & foundPaths & thisFolder ) OR - ( - len( extRoot ) AND directoryExists( - extRoot & "/" & foundPaths & thisFolder - ) - ) + ( len( extRoot ) AND directoryExists( extRoot & "/" & foundPaths & thisFolder ) ) ) { // Save Found Paths foundPaths = foundPaths & thisFolder & "/"; @@ -962,7 +886,6 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { var handler = ""; var action = ""; var newpath = ""; - var httpRequestData = getHTTPRequestData(); var rc = event.getCollection(); /** @@ -978,9 +901,7 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { ( rc[ variables.eventName ] NEQ variables.defaultEvent OR - ( - structKeyExists( url, variables.eventName ) AND rc[ variables.eventName ] EQ variables.defaultEvent - ) + ( structKeyExists( url, variables.eventName ) AND rc[ variables.eventName ] EQ variables.defaultEvent ) ) ) { // New Pathing Calculations if not the default event. If default, relocate to the domain. @@ -1001,19 +922,18 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" { } // Debugging - if ( log.canDebug() ) { - log.debug( - "SES Invalid URL detected. Route: #arguments.route#, script_name: #arguments.script_name#" - ); + if ( variables.log.canDebug() ) { + variables.log.debug( "SES Invalid URL detected. Route: #arguments.route#, script_name: #arguments.script_name#" ); } // Setup Relocation + var httpRequestData = getHTTPRequestData(); var relocationUrl = "#arguments.event.getSESbaseURL()##newpath##serializeURL( httpRequestData.content, arguments.event )#"; if ( httpRequestData.method eq "GET" ) { - cflocation( url=relocationUrl, statusCode=301 ); + cflocation( url = relocationUrl, statusCode = 301 ); } else { - cflocation( url=relocationUrl, statusCode=303 ); + cflocation( url = relocationUrl, statusCode = 303 ); } } } diff --git a/test-harness/Application.cfc b/test-harness/Application.cfc index 8bc484b66..10f8521f4 100644 --- a/test-harness/Application.cfc +++ b/test-harness/Application.cfc @@ -70,7 +70,7 @@ component { } // Bootstrap Reinit - if ( not structKeyExists( application, "cbBootstrap" ) or application.cbBootStrap.isfwReinit() ) { + if ( not structKeyExists( application, "cbBootstrap" ) or structKeyExists( url, "bsReinit" ) ) { lock name="coldbox.bootstrap_#this.name#" type="exclusive" timeout="5" throwonTimeout=true { structDelete( application, "cbBootStrap" ); onApplicationStart(); diff --git a/test-harness/handlers/main.cfc b/test-harness/handlers/main.cfc index 5783cece0..b4233f635 100644 --- a/test-harness/handlers/main.cfc +++ b/test-harness/handlers/main.cfc @@ -50,7 +50,7 @@ component { } function throwException( event, rc, prc ){ - throw( "Whoops!" ); + throw( message : "Whoops!", type : "CustomException" ); } /** diff --git a/test-harness/includes/rev-manifest.json b/test-harness/includes/rev-manifest.json index 044ef5908..e4b012dc7 100644 --- a/test-harness/includes/rev-manifest.json +++ b/test-harness/includes/rev-manifest.json @@ -1,4 +1,5 @@ { "test-harness/includes/js/jquery.js": "/test-harness/includes/js/jquery.1.js", + "test-harness/includes/js/bootstrap.min.js": "/test-harness/includes/js/bootstrap.min.js", "test-harness/modules_app/contentbox-custom/_modules/cctManager/includes/js/cctManager.js": "/modules_app/contentbox-custom/_modules/cctManager/includes/js/cctManager.8613ac4947980152a2e0.js" } \ No newline at end of file diff --git a/test-harness/interceptors/Test1.cfc b/test-harness/interceptors/Test1.cfc index 4ca16eb6d..66b4163af 100755 --- a/test-harness/interceptors/Test1.cfc +++ b/test-harness/interceptors/Test1.cfc @@ -15,6 +15,11 @@ component extends="coldbox.system.Interceptor" { log.info( "Executing request capture" ); } + function afterRendererInit( event, data, rc, prc ){ + log.info( "Executing render init" ); + arguments.data.this.bdd = true; + } + void function onCustomState( event, struct data, rc ){ var threadName = createObject( "java", "java.lang.Thread" ) .currentThread() diff --git a/test-harness/layouts/Main.cfm b/test-harness/layouts/Main.cfm index a74e4cba4..b5c180e3c 100644 --- a/test-harness/layouts/Main.cfm +++ b/test-harness/layouts/Main.cfm @@ -96,6 +96,11 @@ $('.dropdown-toggle').dropdown(); // Tooltips $("[rel=tooltip]").tooltip(); + // Load Exception via AJAX + //$.get( + // "index.cfm/testerror" + //); + }) diff --git a/test-harness/models/ControllerDecorator.cfc b/test-harness/models/ControllerDecorator.cfc index b58776523..f043ee674 100644 --- a/test-harness/models/ControllerDecorator.cfc +++ b/test-harness/models/ControllerDecorator.cfc @@ -3,6 +3,8 @@ component extends="coldbox.system.web.ControllerDecorator" { this.decorator = "true"; function configure(){ + + variables.logger = getLogBox().getLogger( this ); } /** @@ -49,8 +51,12 @@ component extends="coldbox.system.web.ControllerDecorator" { throw( message = "Relocating via relocate: #arguments.toString()#", type = "TestController.relocate" ); } - function runEvent(){ - getLogBox().getLogger( this ).info( " Called decorator runEvent(#arguments.toString()#)" ); + function runEvent( event="" ){ + // useful debugging to pinpoint execution exceptions + logger.info( + "=>Called decorator runEvent(#arguments.toString()#)", + !len( arguments.event ) ? callStackGet() : "" + ); return getController().runEvent( argumentCollection = arguments ); } diff --git a/tests/Application.cfc b/tests/Application.cfc index 1964c0270..bd9b1ea47 100755 --- a/tests/Application.cfc +++ b/tests/Application.cfc @@ -34,7 +34,12 @@ component{ function onRequestStart( required targetPage ){ - //ORMReload(); + // Cleanup + if( !isNull( application.cbController ) ){ + application.cbController.getLoaderService().processShutdown(); + } + structDelete( application, "cbController" ); + structDelete( application, "wirebox" ); return true; } diff --git a/tests/resources/BaseIntegrationTest.cfc b/tests/resources/BaseIntegrationTest.cfc index 231263ed6..36bee6179 100644 --- a/tests/resources/BaseIntegrationTest.cfc +++ b/tests/resources/BaseIntegrationTest.cfc @@ -19,11 +19,34 @@ component appMapping="/cbTestHarness" { + // Load on first test + this.loadColdBox = true; + // Never unload until the request dies + this.unloadColdBox = false; + /*********************************** LIFE CYCLE Methods ***********************************/ function beforeAll(){ super.beforeAll(); // do your own stuff here + structDelete( request, "_lastInvalidEvent" ); + // Wire up the test object with dependencies + if( this.loadColdBox && structKeyExists( application, "wirebox" ) ){ + application.wirebox.autowire( this ); + } + + // add custom matchers + addMatchers( { + toHavePartialKey : function( expectation, args = {} ){ + // iterate over actual to find key + for ( var thisKey in arguments.expectation.actual ) { + if ( findNoCase( arguments.args[ 1 ], thisKey ) ) { + return true; + } + } + return false; + } + } ); } function afterAll(){ diff --git a/tests/runner-async.cfm b/tests/runner-async.cfm index 5881086db..c25be6151 100644 --- a/tests/runner-async.cfm +++ b/tests/runner-async.cfm @@ -9,7 +9,7 @@ - + diff --git a/tests/runner-cachebox.cfm b/tests/runner-cachebox.cfm index 79c9171aa..f47e141ee 100644 --- a/tests/runner-cachebox.cfm +++ b/tests/runner-cachebox.cfm @@ -9,7 +9,7 @@ - + diff --git a/tests/runner-core.cfm b/tests/runner-core.cfm index dc2262f6e..2f883f3dd 100644 --- a/tests/runner-core.cfm +++ b/tests/runner-core.cfm @@ -9,7 +9,7 @@ - + diff --git a/tests/runner-integration.cfm b/tests/runner-integration.cfm index cccc885c6..6fcddeb45 100644 --- a/tests/runner-integration.cfm +++ b/tests/runner-integration.cfm @@ -9,7 +9,7 @@ - + diff --git a/tests/runner-logbox.cfm b/tests/runner-logbox.cfm index 63442fe3d..4f287d6ee 100644 --- a/tests/runner-logbox.cfm +++ b/tests/runner-logbox.cfm @@ -9,7 +9,7 @@ - + diff --git a/tests/runner-wirebox.cfm b/tests/runner-wirebox.cfm index db3915866..7a8f2a27f 100644 --- a/tests/runner-wirebox.cfm +++ b/tests/runner-wirebox.cfm @@ -9,7 +9,7 @@ - + diff --git a/tests/specs/FrameworkSuperTypeTest.cfc b/tests/specs/FrameworkSuperTypeTest.cfc index 9005dc683..9999b95c0 100644 --- a/tests/specs/FrameworkSuperTypeTest.cfc +++ b/tests/specs/FrameworkSuperTypeTest.cfc @@ -1,17 +1,5 @@ component extends="tests.resources.BaseIntegrationTest"{ - /*********************************** LIFE CYCLE Methods ***********************************/ - - // executes before all suites+specs in the run() method - function beforeAll(){ - super.beforeAll(); - } - - // executes after all suites+specs in the run() method - function afterAll(){ - super.afterAll(); - } - /*********************************** BDD SUITES ***********************************/ function run(){ diff --git a/tests/specs/async/AsyncManagerSpec.cfc b/tests/specs/async/AsyncManagerSpec.cfc index fbb928e54..55351e672 100644 --- a/tests/specs/async/AsyncManagerSpec.cfc +++ b/tests/specs/async/AsyncManagerSpec.cfc @@ -6,6 +6,8 @@ component extends="BaseAsyncSpec" { /*********************************** BDD SUITES ***********************************/ function run( testResults, testBox ){ + variables.out = createObject( "java", "java.lang.System" ).out; + // all your suites go here. describe( "ColdBox Async Programming", function(){ beforeEach( function( currentSpec ){ @@ -19,7 +21,7 @@ component extends="BaseAsyncSpec" { .runAsync( function(){ debug( "runAsync: " & getThreadName() ); var message = "hello from in closure land"; - createObject( "java", "java.lang.System" ).out.println( message ); + variables.out.println( message ); debug( "Hello debugger" ); sleep( randRange( 1, 1000 ) ); @@ -171,6 +173,7 @@ component extends="BaseAsyncSpec" { debug( "calculating BMI" ); var combinedFuture = weightFuture.thenCombine( heightFuture, function( weight, height ){ + writeDump( var = arguments, output="console" ); var heightInMeters = arguments.height / 100; return arguments.weight / ( heightInMeters * heightInMeters ); } ); diff --git a/tests/specs/cache/util/EventsURLFacadeTest.cfc b/tests/specs/cache/util/EventsURLFacadeTest.cfc index 87a7f97c1..9af4dfe0e 100755 --- a/tests/specs/cache/util/EventsURLFacadeTest.cfc +++ b/tests/specs/cache/util/EventsURLFacadeTest.cfc @@ -9,12 +9,14 @@ Description : Request service Test -----------------------------------------------------------------------> + cm = createEmptyMock( "coldbox.system.cache.providers.MockProvider" ); cm.$( "getEventCacheKeyPrefix", "mock" ); - facade = new coldbox.system.cache.util.EventURLFacade( cm ); + facade = prepareMock( new coldbox.system.cache.util.EventURLFacade( cm ) ) + .$( "buildAppLink", "http://localhost/test-harness" ); @@ -23,7 +25,9 @@ Request service Test var routedStruct = { name : "luis" }; /* Mocks */ - var context = createMock( "coldbox.system.web.context.RequestContext" ).setRoutedStruct( routedStruct ).setContext( { event : "main.index", id : "123" } ); + var context = createMock( "coldbox.system.web.context.RequestContext" ) + .setRoutedStruct( routedStruct ) + .setContext( { event : "main.index", id : "123" } ); var testHash = facade.getUniqueHash( context ); @@ -38,7 +42,7 @@ Request service Test var testargs = { "id" : 1, "name" : "luis" }; var target = { "incomingHash" : hash( testargs.toString() ), - "cgihost" : CGI.SERVER_NAME + "cgihost" : "http://localhost/test-harness" }; expect( testHash ).toBe( hash( target.toString() ) ); diff --git a/tests/specs/integration/EventCaching.cfc b/tests/specs/integration/EventCaching.cfc index faf65ec87..c9d071595 100755 --- a/tests/specs/integration/EventCaching.cfc +++ b/tests/specs/integration/EventCaching.cfc @@ -2,31 +2,6 @@ extends="tests.resources.BaseIntegrationTest" { - /*********************************** LIFE CYCLE Methods ***********************************/ - - function beforeAll(){ - super.beforeAll(); - // do your own stuff here - - // add custom matchers - addMatchers( { - toHavePartialKey : function( expectation, args = {} ){ - // iterate over actual to find key - for ( var thisKey in arguments.expectation.actual ) { - if ( findNoCase( arguments.args[ 1 ], thisKey ) ) { - return true; - } - } - return false; - } - } ); - } - - function afterAll(){ - // do your own stuff here - super.afterAll(); - } - /*********************************** BDD SUITES ***********************************/ function run(){ diff --git a/tests/specs/integration/EventExecutions.cfc b/tests/specs/integration/EventExecutions.cfc index b292e923a..15f1fd5f6 100644 --- a/tests/specs/integration/EventExecutions.cfc +++ b/tests/specs/integration/EventExecutions.cfc @@ -1,22 +1,11 @@ component extends="tests.resources.BaseIntegrationTest"{ - /*********************************** LIFE CYCLE Methods ***********************************/ - - function beforeAll(){ - super.beforeAll(); - // do your own stuff here - } - - function afterAll(){ - // do your own stuff here - super.afterAll(); - } - /*********************************** BDD SUITES ***********************************/ function run(){ describe( "Event Execution System", function(){ beforeEach( function( currentSpec ){ + structDelete( request, "_lastInvalidEvent" ); // Setup as a new ColdBox request, VERY IMPORTANT. ELSE EVERYTHING LOOKS LIKE THE SAME REQUEST. setup(); } ); @@ -76,13 +65,13 @@ component extends="tests.resources.BaseIntegrationTest"{ story( "I want the onException to have a default status code of 500", function(){ given( "an event that fires an exception", function(){ then( "it should default the status code to 500", function(){ - expect( function(){ - var e = execute( - event = "main.throwException", - renderResults = true, - withExceptionHandling = true - ); - } ).toThrow(); + var e = execute( + event = "main.throwException", + renderResults = true, + withExceptionHandling = true + ); + expecT( e.getPrivateValue( "exception" ).getType() ).toBe( "CustomException" ); + expecT( e.getPrivateValue( "exception" ).getMessage() ).toInclude( "Whoops" ); expect( getNativeStatusCode() ).toBe( 500 ); } ); } ); diff --git a/tests/specs/integration/ExecutorRegistrations.cfc b/tests/specs/integration/ExecutorRegistrations.cfc index 74421ee91..039e4be0a 100644 --- a/tests/specs/integration/ExecutorRegistrations.cfc +++ b/tests/specs/integration/ExecutorRegistrations.cfc @@ -28,6 +28,10 @@ component expect( getController().getAsyncManager().hasExecutor( "resourcesPool" ) ).toBeTrue(); getController().getModuleService().unload( "resourcesTest" ); expect( getController().getAsyncManager().hasExecutor( "resourcesPool" ) ).toBeFalse(); + + // Load it back up, we need it :) + getController().getModuleService().registerAndActivateModule( "resourcesTest" ); + expect( getController().getAsyncManager().hasExecutor( "resourcesPool" ) ).toBeTrue(); } ); } ); } ); diff --git a/tests/specs/integration/MainTests.cfc b/tests/specs/integration/MainTests.cfc index 91f1accc5..d3a471311 100755 --- a/tests/specs/integration/MainTests.cfc +++ b/tests/specs/integration/MainTests.cfc @@ -1,18 +1,9 @@ component extends="tests.resources.BaseIntegrationTest" + autowire { - /*********************************** LIFE CYCLE Methods ***********************************/ - - function beforeAll(){ - super.beforeAll(); - // do your own stuff here - } - - function afterAll(){ - // do your own stuff here - super.afterAll(); - } + property name="logger" inject="logbox:logger:{this}"; /*********************************** BDD SUITES ***********************************/ @@ -25,31 +16,19 @@ structDelete( request, "_lastInvalidEvent" ); } ); - xit( "can render the cache panel", function(){ - // Why can't I just call GET() ACF, why do you make things hard! - var event = this.request( route = "main/cachePanel" ); - expect( event.getRenderedContent() ).toInclude( "cachebox_cache" ); - } ); + it( "can handle autowire annotations for tests", function(){ + expect( variables.logger ).toBeComponent(); + }); + + it( "reads metadata for the test and stores it", function(){ + expect( variables.metadata ).notToBeEmpty(); + }); it( "can handle invalid events", function(){ var event = execute( event = "invalid:bogus.index", renderResults = true ); expect( event.getValue( "cbox_rendered_content" ) ).toInclude( "Invalid Page" ); } ); - it( "can handle invalid onInvalidEvent handlers", function(){ - var originalInvalidEventHandler = getController().getSetting( "invalidEventHandler" ); - getController().setSetting( "invalidEventHandler", "notEvenAnAction" ); - try { - getController().getHandlerService().onConfigurationLoad(); - execute( event = "invalid:bogus.index", renderResults = true ); - fail( "The event handler was invalid and should have thrown an exception" ); - } catch ( HandlerService.InvalidEventHandlerException e ) { - expect( e.message ).toInclude( "The invalidEventHandler event is also invalid" ); - } finally { - getController().setSetting( "invalidEventHandler", originalInvalidEventHandler ); - getController().getHandlerService().onConfigurationLoad(); - } - } ); } ); } diff --git a/tests/specs/integration/ModuleTests.cfc b/tests/specs/integration/ModuleTests.cfc index 506651928..385bedd3b 100644 --- a/tests/specs/integration/ModuleTests.cfc +++ b/tests/specs/integration/ModuleTests.cfc @@ -1,17 +1,5 @@ component extends="tests.resources.BaseIntegrationTest" { - /*********************************** LIFE CYCLE Methods ***********************************/ - - function beforeAll(){ - super.beforeAll(); - // do your own stuff here - } - - function afterAll(){ - // do your own stuff here - super.afterAll(); - } - /*********************************** BDD SUITES ***********************************/ function run(){ diff --git a/tests/specs/integration/Renderings.cfc b/tests/specs/integration/Renderings.cfc index 6defa96d7..48a2b9537 100644 --- a/tests/specs/integration/Renderings.cfc +++ b/tests/specs/integration/Renderings.cfc @@ -2,13 +2,6 @@ component extends="tests.resources.BaseIntegrationTest" { - function beforeAll(){ - // forced cleanup - structDelete( application, "cbController" ); - super.beforeAll(); - // do your own stuff here - } - /*********************************** BDD SUITES ***********************************/ function run(){ @@ -62,31 +55,6 @@ component } ); } ); - story( "I want to listen to when the renderer is created", function(){ - given( "A new renderer", function(){ - then( "I can listen to renderer creations", function(){ - // Mock the listener - var mockListener = createStub(); - mockListener[ "afterRendererInit" ] = variables.afterRendererInit; - - // register it - getController() - .getInterceptorService() - .registerInterceptor( interceptorObject = mockListener, interceptorName = "mockListener" ); - - try { - // Run it - var renderer = getController().getRenderer(); - expect( renderer ).toHaveKey( "bdd" ); - } finally { - getController() - .getInterceptorService() - .unregister( interceptorName = "mockListener", state = "afterRenderReinit" ); - } - } ); - } ); - } ); - story( "I want to be able to render multiple rendering regions", function(){ given( "a rendering region to setView()", function(){ then( "it should render using only its name", function(){ @@ -114,7 +82,6 @@ component } ); } ); - story( "I want to pass through rendering arguments to both layouts and views", function(){ given( "a renderLayout() call with custom arguments", function(){ then( "the view AND layout should receive them", function(){ @@ -126,10 +93,4 @@ component } ); } - function afterRendererInit( event, data ){ - if ( !isNull( arguments.data.this ) ) { - arguments.data.this.bdd = true; - } - } - -} +} \ No newline at end of file diff --git a/tests/specs/integration/RestfulHandlersTests.cfc b/tests/specs/integration/RestfulHandlersTests.cfc index 485f1f193..5e15ad12c 100644 --- a/tests/specs/integration/RestfulHandlersTests.cfc +++ b/tests/specs/integration/RestfulHandlersTests.cfc @@ -1,9 +1,5 @@ component extends="tests.resources.BaseIntegrationTest" { - function beforeAll(){ - super.beforeAll(); - } - function run(){ describe( "ColdBox Restful Handlers", function(){ beforeEach( function( currentSpec ){ @@ -11,12 +7,13 @@ component extends="tests.resources.BaseIntegrationTest" { setup(); } ); - it( "can handle vanilla restful execution", function(){ + it( "can handle vanilla restful execution with custom matchers", function(){ var e = this.get( "/restfulHandler" ); var response = e.getResponse(); expect( response.getError() ).toBeFalse( response.getMessagesString() ); expect( response.getData() ).toBe( "hello" ); + expect( response ).toHaveStatus( 200 ); } ); it( "can handle handler return results", function(){ diff --git a/tests/specs/integration/RestfulTests.cfc b/tests/specs/integration/RestfulTests.cfc index 93107a751..257005d16 100644 --- a/tests/specs/integration/RestfulTests.cfc +++ b/tests/specs/integration/RestfulTests.cfc @@ -10,8 +10,7 @@ component } ); it( "can handle allowed HTTP methods in action annotations", function(){ - prepareMock( getRequestContext() ).$( "getHTTPMethod", "POST" ); - var event = execute( event = "main.actionAllowedMethod", renderResults = true ); + var event = this.POST( "main.actionAllowedMethod" ); expect( event.getRenderedContent() ).toBe( "invalid http: main.actionAllowedMethod" ); } ); @@ -20,16 +19,13 @@ component expect( event.getValue( "cbox_rendered_content" ) ).toBe( "Yep, onInvalidHTTPMethod works!" ); } ); - var formats = [ "json" ]; - // var formats = [ "json", "xml", "pdf", "wddx", "html" ]; - it( "can do #formats.toString()# data renderings", function(){ - for ( var thisFormat in formats ) { - getRequestContext().setValue( "format", thisFormat ); - var event = execute( event = "rendering.index", renderResults = true ); - var prc = event.getCollection( private = true ); - expect( prc.cbox_renderData ).toBeStruct(); - expect( prc.cbox_renderData.contenttype ).toMatch( thisFormat ); - } + it( "can do json data renderings", function(){ + getRequestContext().setValue( "format", "json" ); + var event = execute( event = "rendering.index", renderResults = true ); + var prc = event.getPrivateCollection(); + + expect( prc.cbox_renderData ).toBeStruct(); + expect( prc.cbox_renderData.contenttype ).toMatch( "json" ); } ); it( "can redirect only for html formats with the `formatsRedirect` parameter", function(){ diff --git a/tests/specs/integration/WireBoxDSLTests.cfc b/tests/specs/integration/WireBoxDSLTests.cfc index 3b31fe2e3..169d866dc 100644 --- a/tests/specs/integration/WireBoxDSLTests.cfc +++ b/tests/specs/integration/WireBoxDSLTests.cfc @@ -3,18 +3,6 @@ *******************************************************************************/ component extends="tests.resources.BaseIntegrationTest" { - /*********************************** LIFE CYCLE Methods ***********************************/ - - function beforeAll(){ - super.beforeAll(); - // do your own stuff here - } - - function afterAll(){ - // do your own stuff here - super.afterAll(); - } - /*********************************** BDD SUITES ***********************************/ function run(){ diff --git a/tests/specs/ioc/BuilderTest.cfc b/tests/specs/ioc/BuilderTest.cfc index ec12d1aee..dc279bfad 100755 --- a/tests/specs/ioc/BuilderTest.cfc +++ b/tests/specs/ioc/BuilderTest.cfc @@ -225,7 +225,8 @@ function testregisterCustomBuilders(){ var customDSL = { coolLuis : "coldbox.tests.specs.ioc.dsl.MyTestingDSL" }; - var mockBinder = createMock( "coldbox.system.ioc.config.Binder" ).$( "getCustomDSL", customDSL ); + var mockBinder = createMock( "coldbox.system.ioc.config.Binder" ).setCustomDSL( customDSL ); + mockInjector.setBinder( mockBinder ); builder.registerCustomBuilders(); @@ -239,7 +240,7 @@ dsl : "coolLuis:woopee" }; var customDSL = { coolLuis : "coldbox.tests.specs.ioc.dsl.MyTestingDSL" }; - var mockBinder = createMock( "coldbox.system.ioc.config.Binder" ).$( "getCustomDSL", customDSL ); + var mockBinder = createMock( "coldbox.system.ioc.config.Binder" ).setCustomDSL( customDSL ); mockInjector.setBinder( mockBinder ); builder.registerCustomBuilders(); @@ -327,7 +328,7 @@ dsl : "wirebox:properties" }; props = { prop1 : "hello", name : "luis" }; - mockBinder = createMock( "coldbox.system.ioc.config.Binder" ).$( "getProperties", props ); + mockBinder = createMock( "coldbox.system.ioc.config.Binder" ).setProperties( props ); mockInjector.setBinder( mockBinder ); p = builder.getWireBoxDSL( data ); assertEquals( props, p ); diff --git a/tests/specs/ioc/InjectorTest.cfc b/tests/specs/ioc/InjectorTest.cfc index 056a937d2..77c04bfd1 100755 --- a/tests/specs/ioc/InjectorTest.cfc +++ b/tests/specs/ioc/InjectorTest.cfc @@ -86,8 +86,8 @@ function testRegisterListeners(){ makePublic( injector, "registerListeners" ); - // Mocking - listeners = [ + // Mock listeners + var listeners = [ { class : "coldbox.tests.specs.ioc.config.listeners.MyListener", name : "myDude", @@ -99,8 +99,10 @@ properties : {} } ]; - binder = injector.getBinder(); - prepareMock( binder ).$( "getListeners", listeners ); + injector + .getBinder() + .setListeners( listeners ); + prepareMock( injector.getEventManager() ).$( "register" ); injector.registerListeners(); @@ -115,7 +117,10 @@ properties : {} } ]; - prepareMock( binder ).$( "getListeners", listeners ); + injector + .getBinder() + .setListeners( listeners ); + try { injector.registerListeners(); } catch ( "Injector.ListenerCreationException" e ) { @@ -126,16 +131,19 @@ function testdoScopeRegistration(){ makePublic( injector, "doScopeRegistration" ); - scopeReg = { - key : "wirebox", + + injector.getBinder().scopeRegistration( + key : "mockWireBox", scope : "application" - }; + ); - binder = injector.getBinder(); - prepareMock( binder ).$( "getScopeRegistration", scopeReg ); - injector.doScopeRegistration(); - structKeyExists( application, "wirebox" ); - structDelete( application, "wirebox" ); + try{ + structDelete( application, "mockWireBox" ); + injector.doScopeRegistration(); + structKeyExists( application, "mockWireBox" ); + } finally{ + structDelete( application, "mockWireBox" ); + } } function testConfigureCacheBox(){ @@ -205,17 +213,15 @@ } function testRemoveFromScope(){ - scopeReg = { + injector.getBinder().scopeRegistration( enabled : true, - key : "wirebox", + key : "mockWireBox", scope : "application" - }; - binder = injector.getBinder(); - prepareMock( binder ).$( "getScopeRegistration", scopeReg ); - application.wirebox = createStub(); + ); + application.mockWireBox = createStub(); injector.removeFromScope(); - assertFalse( structKeyExists( application, "wirebox" ) ); + assertFalse( structKeyExists( application, "mockWireBox" ) ); } function testAutowireCallsGetInheritedMetaDataForTargetID(){ diff --git a/tests/specs/ioc/aop/MatcherTest.cfc b/tests/specs/ioc/aop/MatcherTest.cfc index c5b3e18e1..7d6656402 100755 --- a/tests/specs/ioc/aop/MatcherTest.cfc +++ b/tests/specs/ioc/aop/MatcherTest.cfc @@ -9,7 +9,11 @@ var mockMapping = createMock( "coldbox.system.ioc.config.Mapping" ) .init( "UnitTest" ) .setPath( getMetadata( this ).name ) - .$( "getObjectMetadata", getMetadata( this ) ); + .$( + method : "getObjectMetadata", + returns : getMetadata( this ), + preserveReturnType : false + ); // any matcher.any(); @@ -44,7 +48,11 @@ var mockMapping = createMock( "coldbox.system.ioc.config.Mapping" ) .init( "UnitTest" ) .setPath( getMetadata( this ).name ) - .$( "getObjectMetadata", getMetadata( this ) ); + .$( + method : "getObjectMetadata", + returns : getMetadata( this ), + preserveReturnType : false + ); // New AND Matcher andM = createMock( "coldbox.system.aop.Matcher" ).init(); @@ -96,7 +104,11 @@ var mockMapping = createMock( "coldbox.system.ioc.config.Mapping" ) .init( "UnitTest" ) .setPath( getMetadata( this ).name ) - .$( "getObjectMetadata", getMetadata( this ) ); + .$( + method : "getObjectMetadata", + returns : getMetadata( this ), + preserveReturnType : false + ); var fncmd = getMetadata( variables.testMatchMethod ); // any diff --git a/tests/specs/ioc/aop/MixerTest.cfc b/tests/specs/ioc/aop/MixerTest.cfc index 85a2625ca..95b572e23 100755 --- a/tests/specs/ioc/aop/MixerTest.cfc +++ b/tests/specs/ioc/aop/MixerTest.cfc @@ -100,14 +100,14 @@ component extends="tests.resources.BaseIntegrationTest" { } function testBuildClassMatchDictionary(){ - aspects = [ + var aspects = [ { classes : createMock( "coldbox.system.aop.Matcher" ).init().$( "matchClass", true ), methods : createMock( "coldbox.system.aop.Matcher" ).init(), aspects : "Transaction" } ]; - mockBinder.$( "getAspectBindings", aspects ); + mockBinder.setAspectBindings( aspects ); mockMapping = createMock( "coldbox.system.ioc.config.Mapping" ).$( "getName", "unitTest" ); makePublic( mixer, "buildClassMatchDictionary" ); diff --git a/tests/specs/ioc/config/BinderTest.cfc b/tests/specs/ioc/config/BinderTest.cfc index 0b198f30f..d1d219b8c 100755 --- a/tests/specs/ioc/config/BinderTest.cfc +++ b/tests/specs/ioc/config/BinderTest.cfc @@ -12,7 +12,8 @@ this.TYPES = createObject( "component", "coldbox.system.ioc.Types" ); mockInjector = createMock( "coldbox.system.ioc.Injector" ) .setColdBox( createStub().$( "getSetting", "coldbox.test" ) ) - .setEventManager( createStub().$( "announce" ) ); + .setEventManager( createStub().$( "announce" ) ) + .setUtility( new coldbox.system.core.util.Util() ); config = createObject( "component", "coldbox.system.ioc.config.Binder" ).init( injector = mockInjector, config = dataConfigPath @@ -256,21 +257,24 @@ } function testEagerInit(){ - config.mapPath( "Test" ); - mapping = config.getMapping( "Test" ); - assertEquals( "", mapping.isEagerInit() ); + config.mapPath( "tests.resources.Test" ); + mapping = config.getMapping( "Test" ).process( + config, + mockInjector + ); + expect( mapping.isEagerInit() ).toBeFalse(); - config.mapPath( "Test" ).asEagerInit(); + config.mapPath( "tests.resources.Test" ).asEagerInit(); mapping = config.getMapping( "Test" ); assertEquals( true, mapping.isEagerInit() ); } function testNoAutowire(){ - config.mapPath( "Test" ); + config.mapPath( "tests.resources.Test" ); mapping = config.getMapping( "Test" ); - assertEquals( "", mapping.isAutowire() ); + assertEquals( "", mapping.getAutoWire() ); - config.mapPath( "Test" ).noAutowire(); + config.mapPath( "tests.resources.Test" ).noAutowire(); mapping = config.getMapping( "Test" ); assertEquals( false, mapping.isAutowire() ); } @@ -278,7 +282,7 @@ function testWith(){ try { config.with( "Bogus" ); - } catch ( "Binder.InvalidMappingStateException" e ) { + } catch ( "InvalidMappingStateException" e ) { } catch ( Any e ) { fail( e ); } diff --git a/tests/specs/ioc/config/MappingTest.cfc b/tests/specs/ioc/config/MappingTest.cfc index d15536bdf..cd0ab855e 100755 --- a/tests/specs/ioc/config/MappingTest.cfc +++ b/tests/specs/ioc/config/MappingTest.cfc @@ -11,7 +11,6 @@ assertEquals( "UnitTest", mapping.getName() ); } - function testProcessMemento(){ var data = { alias : [ "funky" ], @@ -21,7 +20,7 @@ threadSafe : true, scope : "singleton", cache : { key : "data", timeout : "30" }, - DIConstructorArgs : [ + DIConstructorArguments : [ { name : "val", value : "0" }, { name : "transfer", ref : "transfer" } ], @@ -41,7 +40,7 @@ argName : "MyService" } ], - DIMethodArgs : [ + DIMethodArguments : [ { name : "Joke", value : "You!" }, { name : "service", ref : "MyService" } ] @@ -54,7 +53,7 @@ assertEquals( data.type, mapping.getTYpe() ); assertEquals( data.path, mapping.getPath() ); assertEquals( "init", mapping.getConstructor() ); - assertEquals( "", mapping.isAutowire() ); + assertEquals( "", mapping.getAutoWire() ); assertEquals( true, mapping.isAutoInit() ); assertEquals( true, mapping.isEagerInit() ); assertEquals( data.scope, mapping.getScope() ); @@ -98,7 +97,7 @@ eagerInit : true, scope : "singleton", cache : { key : "data", timeout : "30" }, - DIConstructorArgs : [ + DIConstructorArguments : [ { name : "val", value : "0" }, { name : "transfer", ref : "transfer" } ], @@ -111,7 +110,7 @@ argName : "MyService" } ], - DIMethodArgs : [ + DIMethodArguments : [ { name : "Joke", value : "You!" }, { name : "service", ref : "MyService" } ] @@ -132,7 +131,7 @@ // process memento should have copied over all other data except the ones above assertEquals( data.type, mapping.getTYpe() ); assertEquals( "init", mapping.getConstructor() ); - assertEquals( "", mapping.isAutowire() ); + assertEquals( "", mapping.getAutoWire() ); assertEquals( true, mapping.isAutoInit() ); assertEquals( true, mapping.isEagerInit() ); assertEquals( data.scope, mapping.getScope() ); diff --git a/tests/specs/ioc/config/samples/LogBox.cfc b/tests/specs/ioc/config/samples/LogBox.cfc index 2608cc3f0..ef361544b 100644 --- a/tests/specs/ioc/config/samples/LogBox.cfc +++ b/tests/specs/ioc/config/samples/LogBox.cfc @@ -9,21 +9,14 @@ component { function configure(){ var system = createObject( "java", "java.lang.System" ); - var homeDir = expandPath( "/coldbox/tests" ); + var homeDir = expandPath( "/tests" ); logBox = {}; - // Define Appenders logBox.appenders = { - fileAppender : { - class : "coldbox.system.logging.appenders.RollingFileAppender", - properties : { - fileMaxArchives : 5, - filename : "commandbox", - filepath : homeDir & "/logs", - async : true - } + consoleAppender : { + class : "ConsoleAppender" } }; @@ -31,7 +24,7 @@ component { logBox.root = { levelmax : "INFO", levelMin : "FATAL", - appenders : "fileAppender" + appenders : "consoleAppender" }; } diff --git a/tests/specs/ioc/config/samples/SampleWireBox.cfc b/tests/specs/ioc/config/samples/SampleWireBox.cfc index 8ee702f50..721430b73 100755 --- a/tests/specs/ioc/config/samples/SampleWireBox.cfc +++ b/tests/specs/ioc/config/samples/SampleWireBox.cfc @@ -59,7 +59,7 @@ WireBox injector is created buffer : { path : "java.lang.StringBuilder", type : binder.TYPES.JAVA, - DIConstructorArgs : [ + DIConstructorArguments : [ { name : "buffer", value : "16", diff --git a/tests/specs/ioc/config/samples/WireBox.cfc b/tests/specs/ioc/config/samples/WireBox.cfc index fefc12950..e27d7d1d3 100755 --- a/tests/specs/ioc/config/samples/WireBox.cfc +++ b/tests/specs/ioc/config/samples/WireBox.cfc @@ -59,7 +59,7 @@ WireBox injector is created buffer : { path : "java.lang.StringBuilder", type : this.TYPES.JAVA, - DIConstructorArgs : [ + DIConstructorArguments : [ { name : "buffer", value : "16", diff --git a/tests/specs/web/context/RequestContextTest.cfc b/tests/specs/web/context/RequestContextTest.cfc index 7e5f04e75..28696a272 100755 --- a/tests/specs/web/context/RequestContextTest.cfc +++ b/tests/specs/web/context/RequestContextTest.cfc @@ -20,7 +20,10 @@ component extends="coldbox.system.testing.BaseModelTest" { mockController = getMockController() .$( "getSetting" ) .$args( "modules" ) - .$results( props.modules ); + .$results( props.modules ) + .$( "getSetting" ) + .$args( "AppMapping" ) + .$results( "" ); prepareMock( mockController.getInterceptorService() ); prepareMock( mockController.getWireBox() ); @@ -38,10 +41,7 @@ component extends="coldbox.system.testing.BaseModelTest" { function testGetResponseWhenItExists(){ getRequestContext() - .setPrivateValue( - "response", - getRequestContext().getResponse() - ) + .setPrivateValue( "response", getRequestContext().getResponse() ) .getResponse() .setData( { name : "luis" } ); @@ -51,10 +51,7 @@ component extends="coldbox.system.testing.BaseModelTest" { function testValidRoutes(){ // Mocks - var mockRouter = createStub().$( - "getRoutes", - [ { name : "contactus", pattern : "contactus/" } ] - ); + var mockRouter = createStub().$( "getRoutes", [ { name : "contactus", pattern : "contactus/" } ] ); mockController.getWireBox().$( "getInstance", mockRouter ); var event = getRequestContext().setSESEnabled( true ); @@ -65,10 +62,7 @@ component extends="coldbox.system.testing.BaseModelTest" { function testNamedRoutesWithBuildLink(){ // Mocks - var mockRouter = createStub().$( - "getRoutes", - [ { name : "contactus", pattern : "contactus/" } ] - ); + var mockRouter = createStub().$( "getRoutes", [ { name : "contactus", pattern : "contactus/" } ] ); mockController.getWireBox().$( "getInstance", mockRouter ); var event = getRequestContext().setSESEnabled( true ); @@ -80,10 +74,7 @@ component extends="coldbox.system.testing.BaseModelTest" { function testNamedRoutesWithParamsWithBuildLink(){ // Mocks - var mockRouter = createStub().$( - "getRoutes", - [ { name : "contactus", pattern : "contactus/:id" } ] - ); + var mockRouter = createStub().$( "getRoutes", [ { name : "contactus", pattern : "contactus/:id" } ] ); mockController.getWireBox().$( "getInstance", mockRouter ); var event = getRequestContext().setSESEnabled( true ); @@ -93,7 +84,6 @@ component extends="coldbox.system.testing.BaseModelTest" { expect( r ).toBe( "http://jfetmac/applications/coldbox/test-harness/index.cfm/contactus/3" ); } - function testGetModuleEntryPoint(){ var event = getRequestContext() .setSESEnabled( true ) @@ -109,10 +99,7 @@ component extends="coldbox.system.testing.BaseModelTest" { function testValidModuleRoutes(){ // Mocks var mockRouter = createStub() - .$( - "getModuleRoutes", - [ { name : "home", pattern : "home/" } ] - ) + .$( "getModuleRoutes", [ { name : "home", pattern : "home/" } ] ) .$( "getRoutes", [] ); mockController.getWireBox().$( "getInstance", mockRouter ); @@ -125,9 +112,7 @@ component extends="coldbox.system.testing.BaseModelTest" { ); var r = event.route( "home@mymodule" ); // debug( r ); - expect( r ).toBe( - "http://jfetmac/applications/coldbox/test-harness/index.cfm/mymodule/home/" - ); + expect( r ).toBe( "http://jfetmac/applications/coldbox/test-harness/index.cfm/mymodule/home/" ); } function testInvalidRoute(){ @@ -144,14 +129,10 @@ component extends="coldbox.system.testing.BaseModelTest" { function testGetHTMLBaseURL(){ var event = getRequestContext(); event.setSESEnabled( true ).$( "isSSL", false ); - expect( event.getHTMLBaseURL() ).toinclude( - "http://jfetmac/applications/coldbox/test-harness" - ); + expect( event.getHTMLBaseURL() ).toinclude( "http://jfetmac/applications/coldbox/test-harness" ); event.$( "isSSL", true ); - expect( event.getHTMLBaseURL() ).toinclude( - "https://jfetmac/applications/coldbox/test-harness" - ); + expect( event.getHTMLBaseURL() ).toinclude( "https://jfetmac/applications/coldbox/test-harness" ); } function testgetCollection(){ @@ -200,15 +181,9 @@ component extends="coldbox.system.testing.BaseModelTest" { event.clearCollection(); event.collectionAppend( test ); - assertEquals( - test.today, - event.getValue( "today" ) - ); + assertEquals( test.today, event.getValue( "today" ) ); - assertEquals( - "null", - event.getValue( "invalidVar", "null" ) - ); + assertEquals( "null", event.getValue( "invalidVar", "null" ) ); } function testsetValue(){ @@ -234,10 +209,7 @@ component extends="coldbox.system.testing.BaseModelTest" { assertEquals( test.today, event.getValue( "test" ) ); event.removeValue( "test" ); - assertEquals( - false, - event.getValue( "test", false ) - ); + assertEquals( false, event.getValue( "test", false ) ); } function testvalueExists(){ @@ -309,10 +281,7 @@ component extends="coldbox.system.testing.BaseModelTest" { event.clearCollection(); event.setLayout( layout ); - assertEquals( - layout & ".cfm", - event.getCurrentLayout() - ); + assertEquals( layout & ".cfm", event.getCurrentLayout() ); } function testGetCurrentHandlerWithModule(){ @@ -332,46 +301,25 @@ component extends="coldbox.system.testing.BaseModelTest" { event.setValue( "event", defaultEvent ); - assertEquals( - defaultEvent, - event.getCurrentEvent() - ); + assertEquals( defaultEvent, event.getCurrentEvent() ); assertEquals( "ehTest", event.getCurrentHandler() ); - assertEquals( - "doSomething", - event.getCurrentAction() - ); + assertEquals( "doSomething", event.getCurrentAction() ); defaultEvent = "blog.content.doSomething"; event.setValue( "event", defaultEvent ); - assertEquals( - defaultEvent, - event.getCurrentEvent() - ); + assertEquals( defaultEvent, event.getCurrentEvent() ); assertEquals( "content", event.getCurrentHandler() ); - assertEquals( - "doSomething", - event.getCurrentAction() - ); + assertEquals( "doSomething", event.getCurrentAction() ); defaultEvent = "blog.content.security.doSomething"; event.setValue( "event", defaultEvent ); - assertEquals( - defaultEvent, - event.getCurrentEvent() - ); - assertEquals( - "security", - event.getCurrentHandler() - ); - assertEquals( - "doSomething", - event.getCurrentAction() - ); + assertEquals( defaultEvent, event.getCurrentEvent() ); + assertEquals( "security", event.getCurrentHandler() ); + assertEquals( "doSomething", event.getCurrentAction() ); } function testoverrideEvent(){ @@ -422,46 +370,28 @@ component extends="coldbox.system.testing.BaseModelTest" { var event = getRequestContext(); var centry = structNew(); - assertFalse( - event.isEventCacheable(), - "event cacheable" - ); + assertFalse( event.isEventCacheable(), "event cacheable" ); centry.cacheable = true; centry.test = true; event.setEventCacheableEntry( centry ); - assertTrue( - event.isEventCacheable(), - "event cacheable 2" - ); - assertEquals( - centry, - event.getEventCacheableEntry() - ); + assertTrue( event.isEventCacheable(), "event cacheable 2" ); + assertEquals( centry, event.getEventCacheableEntry() ); } function testViewCacheableEntry(){ var event = getRequestContext(); var centry = structNew(); - assertFalse( - event.isViewCacheable(), - "view cacheable" - ); + assertFalse( event.isViewCacheable(), "view cacheable" ); centry.cacheable = true; centry.test = true; event.setViewCacheableEntry( centry ); - assertTrue( - event.isViewCacheable(), - "view cacheable 2" - ); - assertEquals( - centry, - event.getViewCacheableEntry() - ); + assertTrue( event.isViewCacheable(), "view cacheable 2" ); + assertEquals( centry, event.getViewCacheableEntry() ); } function testRoutedStruct(){ @@ -473,10 +403,7 @@ component extends="coldbox.system.testing.BaseModelTest" { event.setRoutedStruct( routedStruct ); - assertEquals( - event.getRoutedStruct(), - routedStruct - ); + assertEquals( event.getRoutedStruct(), routedStruct ); } function testSES(){ @@ -509,32 +436,17 @@ component extends="coldbox.system.testing.BaseModelTest" { /* simple setup */ event.setSESEnabled( false ); testurl = event.buildLink( "general.index" ); - assertEquals( - testurl, - "index.cfm?event=general.index" - ); + assertEquals( testurl, "index.cfm?event=general.index" ); /* simple qs */ event.setSESEnabled( false ); - testurl = event.buildLink( - to = "general.index", - queryString = "page=2" - ); - assertEquals( - testurl, - "index.cfm?event=general.index&page=2" - ); + testurl = event.buildLink( to = "general.index", queryString = "page=2" ); + assertEquals( testurl, "index.cfm?event=general.index&page=2" ); /* empty qs */ event.setSESEnabled( false ); - testurl = event.buildLink( - to = "general.index", - queryString = "" - ); - assertEquals( - testurl, - "index.cfm?event=general.index" - ); + testurl = event.buildLink( to = "general.index", queryString = "" ); + assertEquals( testurl, "index.cfm?event=general.index" ); /* ses test */ event.setSESEnabled( true ); @@ -550,17 +462,14 @@ component extends="coldbox.system.testing.BaseModelTest" { queryString = "page=2&tests=4", ssl = false ); - assertEquals( - testurl, - base & "/general/index/page/2/tests/4" - ); + assertEquals( testurl, base & "/general/index/page/2/tests/4" ); /* query string as struct transformation */ event.setSESEnabled( true ); event.setsesBaseURL( base ); testurl = event.buildLink( to = "general/index", - queryString = { page:2, tests:4 }, + queryString = { page : 2, tests : 4 }, ssl = false ); expect( testurl ).toInclude( "tests/4" ); @@ -579,10 +488,7 @@ component extends="coldbox.system.testing.BaseModelTest" { ssl = false, queryString = "name=luis&cool=false" ); - assertEquals( - testurl, - base & "/general/index/name/luis/cool/false" - ); + assertEquals( testurl, base & "/general/index/name/luis/cool/false" ); /* translate */ event.setSESEnabled( true ); @@ -603,18 +509,12 @@ component extends="coldbox.system.testing.BaseModelTest" { translate = false, ssl = false ); - assertEquals( - testurl, - base & "/general.index?name=luis&cool=false" - ); + assertEquals( testurl, base & "/general.index?name=luis&cool=false" ); // SES Module Translations event.setSESEnabled( true ); event.setsesBaseURL( base ); - var testUrl = event.buildLink( - to = "test1:main.index", - translate = true - ); + var testUrl = event.buildLink( to = "test1:main.index", translate = true ); expect( testurl ).toBe( "http://www.luismajano.com/index.cfm/test1/main/index" ); } @@ -673,16 +573,10 @@ component extends="coldbox.system.testing.BaseModelTest" { assertEquals( rd.xmlColumnList, "" ); // Test contenttype - event.renderData( - data = "Hello", - contentType = "application/ms-excel" - ); + event.renderData( data = "Hello", contentType = "application/ms-excel" ); rd = event.getRenderData(); assertEquals( rd.type, "html" ); - assertEquals( - rd.contenttype, - "application/ms-excel" - ); + assertEquals( rd.contenttype, "application/ms-excel" ); // Test StatusCodes event.renderData( @@ -723,20 +617,14 @@ component extends="coldbox.system.testing.BaseModelTest" { // debug(event.getCurrentEVent()); assertEquals( "", event.getmoduleRoot() ); event.setValue( "event", "test1:test.home" ); - assertEquals( - props.modules.test1.mapping, - event.getmoduleRoot() - ); + assertEquals( props.modules.test1.mapping, event.getmoduleRoot() ); } function testsetHTTPHeader(){ var event = getRequestContext(); - event.setHTTPHeader( - statusCode = "200", - statusText = "Hello" - ); + event.setHTTPHeader( statusCode = "200", statusText = "Hello" ); event.setHTTPHeader( name = "expires", value = "#now()#" ); } @@ -755,10 +643,7 @@ component extends="coldbox.system.testing.BaseModelTest" { event.noLayout().setView( "test" ); // debug( event.getCollection(private=true) ); - assertEquals( - true, - event.getValue( "layoutOverride", false, true ) - ); + assertEquals( true, event.getValue( "layoutOverride", false, true ) ); } function testDoubleSlashInBuildLink(){ @@ -766,10 +651,7 @@ component extends="coldbox.system.testing.BaseModelTest" { event.setSESEnabled( true ); - link = event.buildLink( - to = "my/event/handler/", - queryString = "one=1&two=2" - ); + link = event.buildLink( to = "my/event/handler/", queryString = "one=1&two=2" ); expect( link ).toInclude( "test-harness/index.cfm/my/event/handler/one/1/two/2" ); // debug( link ); @@ -781,13 +663,7 @@ component extends="coldbox.system.testing.BaseModelTest" { event.setValue( "email", "john@example.com" ); event.setValue( "hackedField", "hacked!" ); - expect( - event.getOnly( [ - "name", - "email", - "field-that-does-not-exist" - ] ) - ).toBe( { "name" : "John", "email" : "john@example.com" } ); + expect( event.getOnly( [ "name", "email", "field-that-does-not-exist" ] ) ).toBe( { "name" : "John", "email" : "john@example.com" } ); } function testOnlyList(){ @@ -804,18 +680,9 @@ component extends="coldbox.system.testing.BaseModelTest" { event.setValue( "name", "John" ); event.setValue( "hackedField", "hacked!" ); event.setValue( "name", "Jane", true ); - event.setValue( - "hackedField", - "hacked as well!", - true - ); + event.setValue( "hackedField", "hacked as well!", true ); - expect( - event.getOnly( - keys = "name,field-that-does-not-exist", - private = true - ) - ).toBe( { "name" : "Jane" } ); + expect( event.getOnly( keys = "name,field-that-does-not-exist", private = true ) ).toBe( { "name" : "Jane" } ); } function testPrivateOnlyMethod(){ @@ -823,11 +690,7 @@ component extends="coldbox.system.testing.BaseModelTest" { event.setValue( "name", "John" ); event.setValue( "hackedField", "hacked!" ); event.setValue( "name", "Jane", true ); - event.setValue( - "hackedField", - "hacked as well!", - true - ); + event.setValue( "hackedField", "hacked as well!", true ); expect( event.getPrivateOnly( [ "name", "field-that-does-not-exist" ] ) ).toBe( { "name" : "Jane" } ); } @@ -838,12 +701,7 @@ component extends="coldbox.system.testing.BaseModelTest" { event.setValue( "email", "john@example.com" ); event.setValue( "hackedField", "hacked!" ); - expect( - event.getExcept( [ - "hackedField", - "field-that-does-not-exist" - ] ) - ).toBe( { "name" : "John", "email" : "john@example.com" } ); + expect( event.getExcept( [ "hackedField", "field-that-does-not-exist" ] ) ).toBe( { "name" : "John", "email" : "john@example.com" } ); } function testExceptList(){ @@ -860,18 +718,9 @@ component extends="coldbox.system.testing.BaseModelTest" { event.setValue( "name", "John" ); event.setValue( "hackedField", "hacked!" ); event.setValue( "name", "Jane", true ); - event.setValue( - "hackedField", - "hacked as well!", - true - ); + event.setValue( "hackedField", "hacked as well!", true ); - expect( - event.getExcept( - keys = "hackedField,key-that-does-not-exist", - private = true - ) - ).toBe( { "name" : "Jane" } ); + expect( event.getExcept( keys = "hackedField,key-that-does-not-exist", private = true ) ).toBe( { "name" : "Jane" } ); } function testPrivateExceptMethod(){ @@ -879,28 +728,30 @@ component extends="coldbox.system.testing.BaseModelTest" { event.setValue( "name", "John" ); event.setValue( "hackedField", "hacked!" ); event.setValue( "name", "Jane", true ); - event.setValue( - "hackedField", - "hacked as well!", - true - ); + event.setValue( "hackedField", "hacked as well!", true ); - expect( - event.getPrivateExcept( [ - "hackedField", - "key-that-does-not-exist" - ] ) - ).toBe( { "name" : "Jane" } ); + expect( event.getPrivateExcept( [ "hackedField", "key-that-does-not-exist" ] ) ).toBe( { "name" : "Jane" } ); } function testGetFullUrl(){ var event = getRequestContext(); + debug( event.getFullUrl() ); + expect( event.getFullUrl() ).toBeTypeOf( "url", "Not an URL" ); + var javaUrl = createObject( "java", "java.net.URL" ).init( event.getFullUrl() ); + } + + function testGetFullUrlWithAppMapping(){ + mockController + .$( "getSetting" ) + .$args( "AppMapping" ) + .$results( "test-harness" ); + + var event = getRequestContext(); + debug( event.getFullUrl() ); expect( event.getFullUrl() ).toBeTypeOf( "url" ); + var javaUrl = createObject( "java", "java.net.URL" ).init( event.getFullUrl() ); - expect( javaUrl.getPort() ).toBe( - listFind( "80,443", CGI.SERVER_PORT ) > 0 ? -1 : CGI.SERVER_PORT - ); } function testUrlMatches(){ diff --git a/tests/specs/web/routing/RoutingServiceTest.cfc b/tests/specs/web/routing/RoutingServiceTest.cfc index 793842fa2..10705d6c9 100755 --- a/tests/specs/web/routing/RoutingServiceTest.cfc +++ b/tests/specs/web/routing/RoutingServiceTest.cfc @@ -5,6 +5,18 @@ routingService = prepareMock( getController().getRoutingService() ); } + function afterAll(){ + // Cleanup due to mods! + + // Graceful shutdown + if( structKeyExists( application, getColdboxAppKey() ) ){ + application[ getColdboxAppKey() ].getLoaderService().processShutdown(); + } + // Wipe app scopes + structDelete( application, getColdboxAppKey() ); + structDelete( application, "wirebox" ); + } + function run(){ describe( "Routing Services", function(){ it( "can clean incoming pathing", function(){ diff --git a/tests/specs/web/services/HandlerServiceTest.cfc b/tests/specs/web/services/HandlerServiceTest.cfc index e51b02b5f..3f207d2b3 100755 --- a/tests/specs/web/services/HandlerServiceTest.cfc +++ b/tests/specs/web/services/HandlerServiceTest.cfc @@ -3,20 +3,6 @@ */ component extends="tests.resources.BaseIntegrationTest" { - /*********************************** LIFE CYCLE Methods ***********************************/ - - // executes before all suites+specs in the run() method - function beforeAll(){ - super.beforeAll(); - } - - // executes after all suites+specs in the run() method - function afterAll(){ - super.afterAll(); - } - - /*********************************** BDD SUITES ***********************************/ - function run( testResults, testBox ){ describe( "Handler Service", function(){ beforeEach( function(){ diff --git a/tests/specs/web/services/loaderserviceTest.cfc b/tests/specs/web/services/loaderserviceTest.cfc index c38612fbf..5130e7c38 100755 --- a/tests/specs/web/services/loaderserviceTest.cfc +++ b/tests/specs/web/services/loaderserviceTest.cfc @@ -1,25 +1,29 @@ component extends="tests.resources.BaseIntegrationTest"{ - function setup(){ - super.setup(); + function run( testResults, testBox ){ - ls = getController().getLoaderService(); - } + describe( "Loader services", function(){ + beforeEach(function( currentSpec ){ + setup(); + ls = getController().getLoaderService(); + }); - function testRegisterHandlers(){ - var context = ""; - var fs = "/"; - var dummyFile = getController().getSetting( "HandlersPath" ) & fs & "dummy.cfc"; - createFile( dummyFile ); - getController().getHandlerService().registerHandlers(); - assertTrue( listFindNoCase( getController().getSetting( "RegisteredHandlers" ), "dummy" ) ); - removeFile( dummyFile ); - } + it( "can register handlers", function(){ + var context = ""; + var dummyFile = getController().getSetting( "HandlersPath" ) & "/dummy.cfc"; + + createFile( dummyFile ); + getController().getHandlerService().registerHandlers(); + try{ + assertTrue( listFindNoCase( getController().getSetting( "RegisteredHandlers" ), "dummy" ) ); + } finally{ + removeFile( dummyFile ); + } + }); - function testProcessShutdown(){ - ls.processShutdown(); + }); } private function createFile( required filename ){ diff --git a/tests/specs/web/services/requestserviceTest.cfc b/tests/specs/web/services/requestserviceTest.cfc index 3b72892e5..f6fe1cad4 100755 --- a/tests/specs/web/services/requestserviceTest.cfc +++ b/tests/specs/web/services/requestserviceTest.cfc @@ -1,134 +1,93 @@ - - - - - - // Call the super setup method to setup the app. - super.setup(); - - - - - - var originalSetting = getcontroller().getSetting( "RequestContextDecorator" ); - - getcontroller().setSetting( "RequestContextDecorator", "" ); - testRequestCaptures(); - getcontroller().setSetting( "RequestContextDecorator", originalSetting ); - - - - - - var service = getController().getRequestService(); - var context = ""; - var persistStruct = structNew(); - var today = now(); - - /* Setup test variables */ - form.name = "luis majano"; - form.event = "ehGeneral.dspHome,movies.list"; - - url.name = "pio majano"; - url.today = today; - - /* Catpure the request */ - context = service.requestCapture(); - - // debug(context.getCollection()); - - /* Tests */ - assertTrue( isObject( context ), "Context Creation" ); - assertTrue( url.today eq context.getValue( "today" ), "URL Append" ); - assertTrue( context.valueExists( "event" ), "Multi-Event Test" ); - - - - - - getController().setSetting( "jsonPayloadToRC", true ); - var mockContext = prepareMock( getController().getRequestService().getContext() ) - .$( "getHTTPContent" ) - .$callback( function( boolean json = false ){ - var payload = { - "name" : "Jon Clausen", - "type" : "JSON" - }; - - if ( json ) { - return payload; - } else { - return serializeJSON( payload ); - } - } ); - var service = prepareMock( getController().getRequestService() ) - .$( "getContext" ) - .$callback( function(){ - return mockContext; - } ); - - /* Catpure the request */ - context = service.requestCapture(); - - debug( context.getCollection() ); - - /* Tests */ - assertTrue( isObject( context ), "Context Creation" ); - assertTrue( context.valueExists( "name" ), "JSON Append" ); - assertTrue( context.valueExists( "type" ), "JSON Append" ); - assertEquals( context.getValue( "type" ), "JSON" ); - - - - - - var service = getController().getRequestService(); - var context = ""; - - /* Setup test variables */ - url.event = "default"; - - /* Catpure the request */ - context = service.requestCapture(); - - /* Tests */ - assertTrue( isObject( context ), "Context Creation" ); - assertTrue( url.event eq context.getCurrentEvent(), "Event mismatch: #context.getCurrentEvent()#" ); - - - - - - var service = getController().getRequestService(); - var context = ""; - - context = service.getContext(); - assertTrue( isObject( context ), "Context Create" ); - - structDelete( request, "cb_requestContext" ); - assertFalse( service.contextExists(), "Context exists" ); - - service.setContext( context ); - assertTrue( structKeyExists( request, "cb_requestContext" ), "setter in request" ); - - - - - - // This errors sometimes on Adobe CF 11 - try { - structClear( cookie ); - } catch ( any e ) { - } - - - +component extends="tests.resources.BaseIntegrationTest"{ + + function run( testResults, testBox ){ + // all your suites go here. + describe( "Request Services", function(){ + + beforeEach(function( currentSpec ){ + setup(); + requestService = getController().getRequestService(); + }); + + it( "can capture requests", function(){ + var today = now(); + + /* Setup test variables */ + form.name = "luis majano"; + form.event = "ehGeneral.dspHome,movies.list"; + + url.name = "pio majano"; + url.today = today; + + /* Catpure the request */ + var context = requestService.requestCapture(); + + // debug(context.getCollection()); + + /* Tests */ + expect( context ).toBeComponent(); + expect( url.today ).toBe( context.getValue( "today" ) ); + expect( url.name ).toBe( context.getValue( "name" ) ); + expect( context.valueExists( "event" ) ).toBeTrue(); + }); + + it( "can capture a json body", function(){ + var mockContext = prepareMock( requestService.getContext() ) + .$( "getHTTPContent" ) + .$callback( function( boolean json = false ){ + var payload = { + "fullName" : "Jon Clausen", + "type" : "JSON" + }; + + if ( json ) { + return payload; + } else { + return serializeJSON( payload ); + } + } ); + // Mock it + request[ "cb_requestContext" ] = mockContext; + + /* Catpure the request */ + var context = requestService.requestCapture(); + + /* Tests */ + expect( context ).toBeComponent(); + expect( context.valueExists( "fullName" ) ).toBeTrue(); + expect( context.valueExists( "type" ) ).toBeTrue(); + expect( context.getValue( "type" ) ).toBe( "JSON" ); + }); + + + it( "can test the default event setup", function(){ + /* Setup test variables */ + form.event = url.event = "photos.index"; + + /* Catpure the request */ + structDelete( request, "cb_requestContext" ); + var context = requestService.requestCapture(); + + /* Tests */ + expect( context ).toBeComponent(); + expect( url.event ).toBe( context.getCurrentEvent() ); + }); + + + it( "can create and check for context in the request scope", function(){ + var context = requestService.getContext(); + expect( context ).toBeComponent(); + expect( requestService.contextExists() ).toBeTrue(); + + structDelete( request, "cb_requestContext" ); + expect( requestService.contextExists() ).toBeFalse(); + + requestService.setContext( context ); + expect( requestService.contextExists() ).toBeTrue(); + expect( request ).toHaveKey( "cb_requestContext" ); + }); + + } ); + } + +} \ No newline at end of file diff --git a/tests/suites/async/performance-parallel-tests.cfm b/tests/suites/async/performance-parallel-tests.cfm new file mode 100755 index 000000000..956ff4b35 --- /dev/null +++ b/tests/suites/async/performance-parallel-tests.cfm @@ -0,0 +1,199 @@ + + function injectState( state ){ + structAppend( variables, arguments.state, true ); + return this; + }; + function getObjectState(){ + return variables.filter( ( k, v ) => !isCustomFunction( v ) || !isClosure( v ) ); + } + + wirebox = new coldbox.system.ioc.Injector(); + mockdata = new testbox.system.modules.mockdatacfc.models.MockData(); + count = 1000; + + // Traditional Sync Approach of creation an state injection + sTime = getTickCount(); + data = mockData.mock( + $num : count, + fname : "name", + lname : "lname", + dob : "date", + id : "uuid", + password : "lorem" + ); + + results = []; + for( x=1 ; x lte count; x++ ){ + thisItem = wirebox.getInstance( "tests.tmp.User" ); + thisItem.injectState = variables.injectState; + + thisItem.injectState( data[ x ] ); + + results.append( thisItem ); + } + + writeDump( var={ + label: "getInstance() for loop", + value : "#getTickCount() - sTime#ms" + } ); + + /*****************************************************/ + /*****************************************************/ + /*****************************************************/ + // Using State Pattern Injection and still using sync processing + + sTime = getTickCount(); + data = mockData.mock( + $num : count, + fname : "name", + lname : "lname", + dob : "date", + id : "uuid", + password : "lorem" + ); + + thisPrototype = wirebox.getInstance( "tests.tmp.User" ); + thisPrototype.injectState = variables.injectState; + thisPrototype.getObjectState = variables.getObjectState; + protoTypeState = thisProtoType.getObjectState(); + + results = []; + for( x=1 ; x lte count; x++ ){ + thisItem = new tests.tmp.User(); + thisItem.injectState = variables.injectState; + + // Inject Both States + thisItem.injectState( protoTypeState ); + thisItem.injectState( data[ x ] ); + + results.append( thisItem ); + } + + writeDump( var={ + label: "State Injection for loop", + value : "#getTickCount() - sTime#ms" + } ); + + + /*****************************************************/ + /*****************************************************/ + /*****************************************************/ + // Using State Pattern Injection + ColdBox Futures with a default 20 thread bound executor + + asyncManager = new coldbox.system.async.AsyncManager(); + + sTime = getTickCount(); + data = mockData.mock( + $num : count, + fname : "name", + lname : "lname", + dob : "date", + id : "uuid", + password : "lorem" + ); + + thisPrototype = wirebox.getInstance( "tests.tmp.User" ); + thisPrototype.injectState = variables.injectState; + thisPrototype.getObjectState = variables.getObjectState; + protoTypeState = thisProtoType.getObjectState(); + + results = asyncManager.newFuture().allApply( + data, + ( record ) => { + var thisItem = new tests.tmp.User(); + thisItem.injectState = variables.injectState; + + // Inject Both States + thisItem.injectState( protoTypeState ); + thisItem.injectState( record ); + + return thisItem; + } + ); + + writeDump( var={ + label: "State injection futures allApply()", + value : "#getTickCount() - sTime#ms" + } ); + + + /*****************************************************/ + /*****************************************************/ + /*****************************************************/ + // Using State Pattern Injection + ColdBox Futures and a Cached Unbounded Thread Pool Executor + + asyncManager = new coldbox.system.async.AsyncManager(); + executor = asyncManager.$executors.newCachedThreadPool(); + + sTime = getTickCount(); + data = mockData.mock( + $num : count, + fname : "name", + lname : "lname", + dob : "date", + id : "uuid", + password : "lorem" + ); + + thisPrototype = wirebox.getInstance( "tests.tmp.User" ); + thisPrototype.injectState = variables.injectState; + thisPrototype.getObjectState = variables.getObjectState; + protoTypeState = thisProtoType.getObjectState(); + + results = asyncManager.newFuture().allApply( + data, + ( record ) => { + var thisItem = new tests.tmp.User(); + thisItem.injectState = variables.injectState; + + // Inject Both States + thisItem.injectState( protoTypeState ); + thisItem.injectState( record ); + + return thisItem; + }, + executor + ); + + writeDump( var={ + label: "State injections futures allApply() with custom executor", + value : "#getTickCount() - sTime#ms" + } ); + executor.shutdown(); + + /*****************************************************/ + /*****************************************************/ + /*****************************************************/ + // Using lucee map in parallel + + sTime = getTickCount(); + data = mockData.mock( + $num : count, + fname : "name", + lname : "lname", + dob : "date", + id : "uuid", + password : "lorem" + ); + + thisPrototype = wirebox.getInstance( "tests.tmp.User" ); + thisPrototype.injectState = variables.injectState; + thisPrototype.getObjectState = variables.getObjectState; + protoTypeState = thisProtoType.getObjectState(); + + results = data.map( ( record ) => { + var thisItem = new tests.tmp.User(); + thisItem.injectState = variables.injectState; + + // Inject Both States + thisItem.injectState( protoTypeState ); + thisItem.injectState( record ); + + return thisItem; + }, true ); + + writeDump( var={ + label: "State injection with native Lucee Parallel Map", + value : "#getTickCount() - sTime#ms" + } ); + \ No newline at end of file diff --git a/tests/suites/eventCachingCollisions/config/Coldbox.cfc b/tests/suites/eventCachingCollisions/config/Coldbox.cfc index 8c34e00c1..ec0820bf6 100755 --- a/tests/suites/eventCachingCollisions/config/Coldbox.cfc +++ b/tests/suites/eventCachingCollisions/config/Coldbox.cfc @@ -48,7 +48,7 @@ // environment settings, create a detectEnvironment() method to detect it yourself. // create a function with the name of the environment so it can be executed if that environment is detected - // the value of the environment is a list of regex patterns to match the CGI.SERVER_NAME. + // the value of the environment is a list of regex patterns to match the http host. environments = { }; diff --git a/tests/suites/eventCachingCollisions/config/routes.cfm b/tests/suites/eventCachingCollisions/config/routes.cfm index d950ef535..a8d447a95 100755 --- a/tests/suites/eventCachingCollisions/config/routes.cfm +++ b/tests/suites/eventCachingCollisions/config/routes.cfm @@ -58,9 +58,9 @@ NOTE: The interceptor will create a new setting called: sesBaseURL with this val Else, htmlBaseURL and sesBaseURL should be the same. ---> - + - +