From d782afc46687e945c62d618e82ba47fbcae981b3 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 17 Sep 2019 11:06:27 -0500 Subject: [PATCH 001/102] Bump for 4.9.0 --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index 9bd30514b..9694f9c40 100644 --- a/build/build.xml +++ b/build/build.xml @@ -16,7 +16,7 @@ External Dependencies: - + From ab269a1ad2d570beb72a289b73cd31c7700165af Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 17 Sep 2019 11:29:22 -0500 Subject: [PATCH 002/102] Bump loader version in 4.8 build --- build/build.properties | 2 +- build/build.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/build.properties b/build/build.properties index 97ca07a9a..121d08aa5 100644 --- a/build/build.properties +++ b/build/build.properties @@ -11,7 +11,7 @@ java.debug=true #dependencies dependencies.dir=${basedir}/lib cfml.version=5.2.9.31 -cfml.loader.version=2.3.0 +cfml.loader.version=2.3.1 cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} lucee.config.version=5.2.4.37 diff --git a/build/build.xml b/build/build.xml index 9694f9c40..9bd30514b 100644 --- a/build/build.xml +++ b/build/build.xml @@ -16,7 +16,7 @@ External Dependencies: - + From cf22b7aaf370d9a2898eb86e9debe3d40a8b9d5e Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 17 Sep 2019 11:53:12 -0500 Subject: [PATCH 003/102] Re-bump for 4.9 --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index 9bd30514b..9694f9c40 100644 --- a/build/build.xml +++ b/build/build.xml @@ -16,7 +16,7 @@ External Dependencies: - + From 179b05acc37847c67e4be0f00e882e3ef3240f92 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 17 Sep 2019 14:50:56 -0500 Subject: [PATCH 004/102] Bump bundled JRE version --- build/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.properties b/build/build.properties index 121d08aa5..1ca044f62 100644 --- a/build/build.properties +++ b/build/build.properties @@ -16,7 +16,7 @@ cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} lucee.config.version=5.2.4.37 jre.adoptVesionr=openjdk8 -jre.version=jdk8u202-b08 +jre.version=jdk8u222-b10 launch4j.version=3.12 runwar.version=3.8.1-SNAPSHOT jline.version=3.10.0 From f71fd2c0141f0f535bc0c55e836cc0145568bdcd Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 17 Sep 2019 21:50:36 -0500 Subject: [PATCH 005/102] COMMANDBOX-1049 --- .../modules_app/system-commands/commands/info.cfc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/cfml/system/modules_app/system-commands/commands/info.cfc b/src/cfml/system/modules_app/system-commands/commands/info.cfc index b1111f313..4f3958a96 100644 --- a/src/cfml/system/modules_app/system-commands/commands/info.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/info.cfc @@ -25,6 +25,16 @@ component aliases="about" { var userName = getSystemSetting( 'user.name', 'Unkonwn' ); var javaBinary = fileSystemUtil.getJREExecutable(); var JLineTerminal = shell.getReader().getTerminal().getClass().getName(); + var runwarVersion = 'Unknown'; + try { + var runwarClass = createObjecT( 'java', 'runwar.Server' ); + runwarVersion = runwarClass.getVersion(); + + var runwarJarPath = createObject( "java", "java.io.File" ) + .init( runwarClass.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().getSchemeSpecificPart() ).getAbsolutePath(); + + runwarVersion &= ' (#runwarJarPath#)' + }catch( any e ) {} print.line(); print.greenLine( '****************************************************************************************************' ); @@ -44,6 +54,7 @@ component aliases="about" { print.green( '*' ); print.cyan( ' Java Path: ' ); print.text( '#javaBinary##repeatString( ' ', max( 0, width - 24 - len( javaBinary ) ) )#' ); print.greenLine( '*' ); print.green( '*' ); print.cyan( ' OS Username ' ); print.text( '#userName##repeatString( ' ', max( 0, width - 24 - len( userName ) ) )#' ); print.greenLine( '*' ); print.green( '*' ); print.cyan( ' JLine Terminal ' ); print.text( '#JLineTerminal##repeatString( ' ', max( 0, width - 24 - len( JLineTerminal ) ) )#' ); print.greenLine( '*' ); + print.green( '*' ); print.cyan( ' Runwar Version ' ); print.text( '#runwarVersion##repeatString( ' ', max( 0, width - 24 - len( runwarVersion ) ) )#' ); print.greenLine( '*' ); print.greenLine( '* *' ); print.greenLine( '* *' ); print.greenLine( '****************************************************************************************************' ); From b26988122c98b25259c9358459b790fcaff62943 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 20 Sep 2019 15:06:02 -0500 Subject: [PATCH 006/102] COMMANDBOX-963 --- src/java/cliloader/LoaderCLIMain.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/java/cliloader/LoaderCLIMain.java b/src/java/cliloader/LoaderCLIMain.java index 99d8666ca..b6e206e09 100644 --- a/src/java/cliloader/LoaderCLIMain.java +++ b/src/java/cliloader/LoaderCLIMain.java @@ -605,6 +605,8 @@ public static ArrayList< String > initialize( String[] arguments ) throws IOExce props.setProperty( "cfml.cli.lib", libDir.getAbsolutePath() ); File cfmlDir = new File( cli_home.getPath() + "/cfml" ); File cfmlSystemDir = new File( cli_home.getPath() + "/cfml/system" ); + File cfmlBundlesDir = new File( cli_home.getPath() + "/engine/cfml/cli/lucee-server/bundles" ); + File cfmlFelixCacheDir = new File( cli_home.getPath() + "/engine/cfml/cli/lucee-server/felix-cache" ); // clean out any leftover pack files (an issue on windows) Util.cleanUpUnpacked( libDir ); @@ -641,6 +643,17 @@ public static ArrayList< String > initialize( String[] arguments ) throws IOExce if( cfmlSystemDir.exists() ) { Util.deleteDirectory( cfmlSystemDir ); } + + // OSGI can be grumpy on uprade with comppeting bundles. Start fresh + if( cfmlBundlesDir.exists() ) { + log.info( "Cleaning old OSGI Bundles..." ); + Util.deleteDirectory( cfmlBundlesDir ); + } + // OSGI can be grumpy on uprade with comppeting bundles. Start fresh + if( cfmlSystemDir.exists() ) { + Util.deleteDirectory( cfmlFelixCacheDir ); + } + Util.unzipInteralZip( classLoader, CFML_ZIP_PATH, cfmlDir, debug ); Util.unzipInteralZip( classLoader, ENGINECONF_ZIP_PATH, new File( From 3ffa196a8e41ec94cc6d08a9208465529040e256 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 20 Sep 2019 15:54:06 -0500 Subject: [PATCH 007/102] Trying out Lucee 5.3 and Java 11 COMMANDBOX-963 --- build/build.properties | 8 ++++---- build/build.xml | 10 ++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/build/build.properties b/build/build.properties index 1ca044f62..571a5b49d 100644 --- a/build/build.properties +++ b/build/build.properties @@ -10,13 +10,13 @@ java.debug=true #dependencies dependencies.dir=${basedir}/lib -cfml.version=5.2.9.31 -cfml.loader.version=2.3.1 +cfml.version=5.3.5.10-SNAPSHOT +cfml.loader.version=2.3.2 cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} lucee.config.version=5.2.4.37 -jre.adoptVesionr=openjdk8 -jre.version=jdk8u222-b10 +jre.adoptVesionr=openjdk11 +jre.version=jdk-11.0.4+11 launch4j.version=3.12 runwar.version=3.8.1-SNAPSHOT jline.version=3.10.0 diff --git a/build/build.xml b/build/build.xml index 9694f9c40..86404a002 100644 --- a/build/build.xml +++ b/build/build.xml @@ -779,6 +779,7 @@ External Dependencies: + @@ -790,7 +791,8 @@ External Dependencies: - @@ -808,7 +810,7 @@ External Dependencies: - @@ -823,7 +825,7 @@ External Dependencies: - @@ -838,7 +840,7 @@ External Dependencies: - From a3f93728457cb33c2cfffa3f5d3e0ca70916d6ec Mon Sep 17 00:00:00 2001 From: Adrian Sanchez <44038609+msis-adrian@users.noreply.github.com> Date: Tue, 24 Sep 2019 13:50:46 -0700 Subject: [PATCH 008/102] bind permission error, likely not running root privileges. (#206) * permission denied error, likely not running root privileges. That message is probably unprofessional so you might want to change it. * Update message --- src/cfml/system/services/ServerService.cfc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 13dc87fe7..25c1307f2 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -1726,6 +1726,10 @@ component accessors="true" singleton { if( e.message contains 'Cannot assign requested address' || e.message contains 'Can''t assign requested address' ) { return true; } + if( e.message contains 'Permission denied' ) { + consoleLogger.debug( e.message); + consoleLogger.error( "Permission to bind the port was denied. This likely means you need to run as root or pick a port above 1024."); + } // We're assuming that any other error means the address was in use. // Java doesn't provide a specific message or exception type for this unfortunately. return false; From 314f824efcc98604a50a9ce3fd2e210c10bbce5c Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 30 Sep 2019 10:14:59 -0500 Subject: [PATCH 009/102] COMMANDBOX-1052 #resolve Copy the link to this issue New TestBox commands: generate visualizer and generate browser --- .../commands/testbox/generate/browser | 13 - .../commands/testbox/generate/browser.cfc | 40 + .../commands/testbox/generate/harness.cfc | 28 +- .../commands/testbox/generate/visualizer.cfc | 40 + .../testbox/test-browser/Application.cfc | 2 +- .../templates/testbox/test-browser/index.cfm | 200 +- .../templates/testbox/test-results.json | 1619 ++++ .../testbox/{ => test-visualizer}/bdd.txt | 0 .../testbox/test-visualizer/index.html | 556 ++ .../testbox/test-visualizer/main.css | 7615 +++++++++++++++++ 10 files changed, 9946 insertions(+), 167 deletions(-) delete mode 100644 src/cfml/system/modules_app/testbox-commands/commands/testbox/generate/browser create mode 100644 src/cfml/system/modules_app/testbox-commands/commands/testbox/generate/browser.cfc create mode 100644 src/cfml/system/modules_app/testbox-commands/commands/testbox/generate/visualizer.cfc create mode 100644 src/cfml/system/modules_app/testbox-commands/templates/testbox/test-results.json rename src/cfml/system/modules_app/testbox-commands/templates/testbox/{ => test-visualizer}/bdd.txt (100%) create mode 100644 src/cfml/system/modules_app/testbox-commands/templates/testbox/test-visualizer/index.html create mode 100644 src/cfml/system/modules_app/testbox-commands/templates/testbox/test-visualizer/main.css diff --git a/src/cfml/system/modules_app/testbox-commands/commands/testbox/generate/browser b/src/cfml/system/modules_app/testbox-commands/commands/testbox/generate/browser deleted file mode 100644 index b5a8e2e68..000000000 --- a/src/cfml/system/modules_app/testbox-commands/commands/testbox/generate/browser +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Description of command - **/ -component extends="cli.BaseCommand" aliases="" excludeFromHelp=false { - - /** - * - **/ - function run( ) { - print.line( "Command not implemented!" ); - } - -} \ No newline at end of file diff --git a/src/cfml/system/modules_app/testbox-commands/commands/testbox/generate/browser.cfc b/src/cfml/system/modules_app/testbox-commands/commands/testbox/generate/browser.cfc new file mode 100644 index 000000000..0dc28609e --- /dev/null +++ b/src/cfml/system/modules_app/testbox-commands/commands/testbox/generate/browser.cfc @@ -0,0 +1,40 @@ +/** + * Create a new TestBox test browser for an application. The test browser will be created in a directory called tests/test-browser. + * . + * You can run it from the root of your application. + * {code:bash} + * testbox create browser + * {code} + * . + * Or pass the base directory of your application as a parameter + * {code:bash} + * testbox create browser C:\myApp + * {code} + */ +component { + + /** + * @directory The base directory to create your test browser + */ + function run( string directory = getCWD() ) { + + // This will make each directory canonical and absolute + arguments.directory = resolvePath( arguments.directory & "/tests/test-browser" ); + + // Validate directory + if( !directoryExists( arguments.directory ) ) { + directoryCreate( arguments.directory ); + + // Copy template + directoryCopy( '/testbox-commands/templates/testbox/test-browser/', arguments.directory, true ); + + // Print the results to the console + print.greenLine( 'Created ' & arguments.directory ); + } + else { + error( "Directory #arguments.directory# already exists!" ); + } + + } + +} diff --git a/src/cfml/system/modules_app/testbox-commands/commands/testbox/generate/harness.cfc b/src/cfml/system/modules_app/testbox-commands/commands/testbox/generate/harness.cfc index 1e8a71aaa..9c20c037e 100644 --- a/src/cfml/system/modules_app/testbox-commands/commands/testbox/generate/harness.cfc +++ b/src/cfml/system/modules_app/testbox-commands/commands/testbox/generate/harness.cfc @@ -1,21 +1,21 @@ /** -* Create a new TestBox test harness for an application. The test harness will be created in a directory called tests. -* . -* You can run it from the root of your application. -* {code:bash} -* testbox create harness -* {code} -* . -* Or pass the base directory of your application as a parameter -* {code:bash} -* testbox create harness C:\myApp -* {code} -**/ + * Create a new TestBox test harness for an application. The test harness will be created in a directory called tests. + * . + * You can run it from the root of your application. + * {code:bash} + * testbox create harness + * {code} + * . + * Or pass the base directory of your application as a parameter + * {code:bash} + * testbox create harness C:\myApp + * {code} + */ component { /** - * @directory The base directory to create your test harness - **/ + * @directory The base directory to create your test harness + */ function run( string directory = getCWD() ) { // This will make each directory canonical and absolute diff --git a/src/cfml/system/modules_app/testbox-commands/commands/testbox/generate/visualizer.cfc b/src/cfml/system/modules_app/testbox-commands/commands/testbox/generate/visualizer.cfc new file mode 100644 index 000000000..d7a3753af --- /dev/null +++ b/src/cfml/system/modules_app/testbox-commands/commands/testbox/generate/visualizer.cfc @@ -0,0 +1,40 @@ +/** + * Create a new TestBox test visualizer for an application. The test harness will be created in a directory called tests/test-visualizer. + * . + * You can run it from the root of your application. + * {code:bash} + * testbox create visualizer + * {code} + * . + * Or pass the base directory of your application as a parameter + * {code:bash} + * testbox create visualizer C:\myApp + * {code} + */ +component { + + /** + * @directory The base directory to create your test visualizer + */ + function run( string directory = getCWD() ) { + + // This will make each directory canonical and absolute + arguments.directory = resolvePath( arguments.directory & "/tests/test-visualizer" ); + + // Validate directory + if( !directoryExists( arguments.directory ) ) { + directoryCreate( arguments.directory ); + + // Copy template + directoryCopy( '/testbox-commands/templates/testbox/test-visualizer/', arguments.directory, true ); + + // Print the results to the console + print.greenLine( 'Created ' & arguments.directory ); + } + else { + error("Directory #arguments.directory# already exists!"); + } + + } + +} \ No newline at end of file diff --git a/src/cfml/system/modules_app/testbox-commands/templates/testbox/test-browser/Application.cfc b/src/cfml/system/modules_app/testbox-commands/templates/testbox/test-browser/Application.cfc index 309faaf18..530ed616b 100644 --- a/src/cfml/system/modules_app/testbox-commands/templates/testbox/test-browser/Application.cfc +++ b/src/cfml/system/modules_app/testbox-commands/templates/testbox/test-browser/Application.cfc @@ -9,7 +9,7 @@ component{ this.sessionManagement = true; // any mappings go here, we create one that points to the root called test. - this.mappings[ "/test" ] = getDirectoryFromPath( getCurrentTemplatePath() ); + this.mappings[ "/tests" ] = getDirectoryFromPath( getCurrentTemplatePath() ); // any orm definitions go here. diff --git a/src/cfml/system/modules_app/testbox-commands/templates/testbox/test-browser/index.cfm b/src/cfml/system/modules_app/testbox-commands/templates/testbox/test-browser/index.cfm index d219f0d20..48c5ec4ff 100644 --- a/src/cfml/system/modules_app/testbox-commands/templates/testbox/test-browser/index.cfm +++ b/src/cfml/system/modules_app/testbox-commands/templates/testbox/test-browser/index.cfm @@ -1,6 +1,8 @@ + + - + @@ -11,27 +13,26 @@ - - + #testbox.init( directory=rootMapping & url.path ).run()# -

Invalid incoming directory: #rootMapping & url.path#

+

Invalid incoming directory: #rootMapping & url.path#

- + @@ -40,155 +41,76 @@
+ - TestBox Global Runner - - + + + + + - }); - function runTests(){ - $("#btn-run").html( 'Running...' ).css( "opacity", "0.5" ); - $("#tb-results").load( "index.cfm", $("#runnerForm").serialize(), function( data ){ - $("#btn-run").html( 'Run' ).css( "opacity", "1" ); - } ); - } - function clearResults(){ - $("#tb-results").html( '' ); - $("#target").html( '' ); - $("#labels").html( '' ); - } - - -
-
- - -
-
v#testbox.getVersion()#
- - +
+
+
+ +
+ v#testbox.getVersion()# +
+ +
- -
-

TestBox Test Browser:

-

- Below is a listing of the files and folders starting from your root #rootPath#. You can click on individual tests in order to execute them - or click on the Run All button on your left and it will execute a directory runner from the visible folder. -

- -
Contents: #executePath# - -

-
- - - - - - - - +#qResults.name#
- - #qResults.name#
- -
- - #qResults.name#
-
- -
-
- +
+
+ + +

TestBox Test Browser:

+

+ Below is a listing of the files and folders starting from your root #rootPath#. You can click on individual tests in order to execute them + or click on the Run All button on your left and it will execute a directory runner from the visible folder. +

+ +
+ Contents: #executePath# + +

+
+ + + + + + + + #qResults.name#
+ + target="_blank"
>#qResults.name#
+ + target="_blank">#qResults.name#
+ + #qResults.name#
+ + +
+
+ +
- -
- -
- - + \ No newline at end of file diff --git a/src/cfml/system/modules_app/testbox-commands/templates/testbox/test-results.json b/src/cfml/system/modules_app/testbox-commands/templates/testbox/test-results.json new file mode 100644 index 000000000..82954370c --- /dev/null +++ b/src/cfml/system/modules_app/testbox-commands/templates/testbox/test-results.json @@ -0,0 +1,1619 @@ +{ + "totalSuites": 3, + "startTime": 1553009673073, + "bundleStats": [{ + "totalSuites": 1, + "startTime": 1553009673088, + "totalPass": 1, + "totalDuration": 35, + "totalSkipped": 0, + "totalFail": 1, + "totalSpecs": 2, + "path": "tests.specsWithFailures.TeardownWithFailureTest", + "endTime": 1553009673123, + "totalError": 0, + "name": "tests.specsWithFailures.TeardownWithFailureTest", + "id": "7FB7EEAB1B9B025D803C404484858BC4", + "suiteStats": [{ + "startTime": 1553009673095, + "totalPass": 1, + "totalDuration": 26, + "totalSkipped": 0, + "totalFail": 1, + "totalSpecs": 2, + "bundleID": "7FB7EEAB1B9B025D803C404484858BC4", + "status": "Failed", + "parentID": "", + "specStats": [{ + "error": {}, + "startTime": 1553009673096, + "totalDuration": 7, + "failOrigin": {}, + "status": "Passed", + "suiteID": "C661C0D148339356A96A2DE9010FC851", + "endTime": 1553009673103, + "name": "stonesSuccessTest", + "id": "51F16F531C908CE0995A709E3E103A05", + "failMessage": "" + }, { + "error": {}, + "startTime": 1553009673103, + "totalDuration": 18, + "failOrigin": [{ + "Raw_Trace": "specswithfailures.teardownwithfailuretest_cfc$cf.udfCall(/tests/specsWithFailures/TeardownWithFailureTest.cfc:33)", + "codePrintPlain": "31: \n32: \t\t\n33: \t\t\n34: \t\n35: \n", + "column": 0, + "line": 33, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/specsWithFailures/TeardownWithFailureTest.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "31:
\n32: \t\t<!--- make some assertion based on the result of exercising the component --->
\n33: \t\t<cfset assertEquals("Keith Richards",result,"This is really NOT equal")>
\n34: \t</cffunction>
\n35:
\n" + }, { + "Raw_Trace": "system.basespec_cfc$cf.udfCall5(/testbox/system/BaseSpec.cfc:993)", + "codePrintPlain": "991: \t\t\t\t// Execute Spec\n992: \t\t\t\ttry{\n993: \t\t\t\t\tinvoke( this, arguments.spec.name );\n994: \n995: \t\t\t\t\t// Where we expecting an exception and it did not throw?\n", + "column": 0, + "line": 993, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/BaseSpec.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "991: \t\t\t\t// Execute Spec
\n992: \t\t\t\ttry{
\n993: \t\t\t\t\tinvoke( this, arguments.spec.name );
\n994:
\n995: \t\t\t\t\t// Where we expecting an exception and it did not throw?
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:208)", + "codePrintPlain": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,\n207: \t\t\t\t\t\trunner=this\n208: \t\t\t\t\t);\n209: \n210: \t\t\t\t\t// verify call backs\n", + "column": 0, + "line": 208, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,
\n207: \t\t\t\t\t\trunner=this
\n208: \t\t\t\t\t);
\n209:
\n210: \t\t\t\t\t// verify call backs
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:81)", + "codePrintPlain": "79: \t\t\t\t\t\ttestResults=arguments.testResults,\n80: \t\t\t\t\t\tbundleStats=bundleStats,\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks\n82: \t\t\t\t\t);\n83: \n", + "column": 0, + "line": 81, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "79: \t\t\t\t\t\ttestResults=arguments.testResults,
\n80: \t\t\t\t\t\tbundleStats=bundleStats,
\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks
\n82: \t\t\t\t\t);
\n83:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall2(/testbox/system/TestBox.cfc:478)", + "codePrintPlain": "476: \t\t\t\t// Run via xUnit Style\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );\n479: \t\t\t}\n480: \t\t} catch( Any e ){\n", + "column": 0, + "line": 478, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "476: \t\t\t\t// Run via xUnit Style
\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )
\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );
\n479: \t\t\t}
\n480: \t\t} catch( Any e ){
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:251)", + "codePrintPlain": "249: \t\t\t\tbundlePath = thisBundlePath,\n250: \t\t\t\ttestResults = results,\n251: \t\t\t\tcallbacks = arguments.callbacks\n252: \t\t\t);\n253: \n", + "column": 0, + "line": 251, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "249: \t\t\t\tbundlePath = thisBundlePath,
\n250: \t\t\t\ttestResults = results,
\n251: \t\t\t\tcallbacks = arguments.callbacks
\n252: \t\t\t);
\n253:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:160)", + "codePrintPlain": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }\n159: \t\t// run it and get results\n160: \t\tvar results = runRaw( argumentCollection=arguments );\n161: \t\t// store latest results\n162: \t\tvariables.result = results;\n", + "column": 0, + "line": 160, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }
\n159: \t\t// run it and get results
\n160: \t\tvar results = runRaw( argumentCollection=arguments );
\n161: \t\t// store latest results
\n162: \t\tvariables.result = results;
\n" + }, { + "Raw_Trace": "system.runners.htmlrunner_cfm$cf.call(/testbox/system/runners/HTMLRunner.cfm:49)", + "codePrintPlain": "47: \n48: // Run Tests using correct reporter\n49: results = testbox.run( reporter=url.reporter );\n50: \n51: // Write TEST.properties in report destination path.\n", + "column": 0, + "line": 49, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/HTMLRunner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "47:
\n48: // Run Tests using correct reporter
\n49: results = testbox.run( reporter=url.reporter );
\n50:
\n51: // Write TEST.properties in report destination path.
\n" + }], + "status": "Failed", + "suiteID": "C661C0D148339356A96A2DE9010FC851", + "endTime": 1553009673121, + "name": "stonesFailTest", + "id": "63245483E59F4CDF07004CDB9D60F510", + "failMessage": "This is really NOT equal. Expected [Keith Richards] Actual [Mick Jagger]" + }], + "endTime": 1553009673121, + "totalError": 0, + "name": "tests.specsWithFailures.TeardownWithFailureTest", + "id": "C661C0D148339356A96A2DE9010FC851", + "suiteStats": [] + }], + "globalException": "" + }, { + "totalSuites": 1, + "startTime": 1553009673136, + "totalPass": 0, + "totalDuration": 31, + "totalSkipped": 0, + "totalFail": 0, + "totalSpecs": 7, + "path": "tests.specsWithFailures.MXUnitExpectedExceptions", + "endTime": 1553009673167, + "totalError": 7, + "name": "tests.specsWithFailures.MXUnitExpectedExceptions", + "id": "46D12A8CA3F9A0EBC5DF1F2A51147864", + "suiteStats": [{ + "startTime": 1553009673136, + "totalPass": 0, + "totalDuration": 31, + "totalSkipped": 0, + "totalFail": 0, + "totalSpecs": 7, + "bundleID": "46D12A8CA3F9A0EBC5DF1F2A51147864", + "status": "Error", + "parentID": "", + "specStats": [{ + "error": { + "Extended_Info": "", + "Message": "java.util.ConcurrentModificationException", + "Detail": "", + "additional": {}, + "TagContext": [{ + "Raw_Trace": "specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall1(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc:18)", + "codePrintPlain": "16: \n17: \tfunction teardown(){\n18: \t\tstructClear( request );\n19: \t}\n20: \n", + "column": 0, + "line": 18, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/specsWithFailures/MXUnitExpectedExceptions.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "16:
\n17: \tfunction teardown(){
\n18: \t\tstructClear( request );
\n19: \t}
\n20:
\n" + }, { + "Raw_Trace": "system.basespec_cfc$cf.udfCall5(/testbox/system/BaseSpec.cfc:1011)", + "codePrintPlain": "1009: \t\t\t\t\t// execute teardown()\n1010: \t\t\t\t\tif( structKeyExists( this, \"teardown\" ) ){ this.teardown( currentMethod=arguments.spec.name ); }\n1011: \t\t\t\t}\n1012: \n1013: \t\t\t\t// store spec status\n", + "column": 0, + "line": 1011, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/BaseSpec.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "1009: \t\t\t\t\t// execute teardown()
\n1010: \t\t\t\t\tif( structKeyExists( this, "teardown" ) ){ this.teardown( currentMethod=arguments.spec.name ); }
\n1011: \t\t\t\t}
\n1012:
\n1013: \t\t\t\t// store spec status
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:208)", + "codePrintPlain": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,\n207: \t\t\t\t\t\trunner=this\n208: \t\t\t\t\t);\n209: \n210: \t\t\t\t\t// verify call backs\n", + "column": 0, + "line": 208, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,
\n207: \t\t\t\t\t\trunner=this
\n208: \t\t\t\t\t);
\n209:
\n210: \t\t\t\t\t// verify call backs
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:81)", + "codePrintPlain": "79: \t\t\t\t\t\ttestResults=arguments.testResults,\n80: \t\t\t\t\t\tbundleStats=bundleStats,\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks\n82: \t\t\t\t\t);\n83: \n", + "column": 0, + "line": 81, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "79: \t\t\t\t\t\ttestResults=arguments.testResults,
\n80: \t\t\t\t\t\tbundleStats=bundleStats,
\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks
\n82: \t\t\t\t\t);
\n83:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall2(/testbox/system/TestBox.cfc:478)", + "codePrintPlain": "476: \t\t\t\t// Run via xUnit Style\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );\n479: \t\t\t}\n480: \t\t} catch( Any e ){\n", + "column": 0, + "line": 478, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "476: \t\t\t\t// Run via xUnit Style
\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )
\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );
\n479: \t\t\t}
\n480: \t\t} catch( Any e ){
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:251)", + "codePrintPlain": "249: \t\t\t\tbundlePath = thisBundlePath,\n250: \t\t\t\ttestResults = results,\n251: \t\t\t\tcallbacks = arguments.callbacks\n252: \t\t\t);\n253: \n", + "column": 0, + "line": 251, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "249: \t\t\t\tbundlePath = thisBundlePath,
\n250: \t\t\t\ttestResults = results,
\n251: \t\t\t\tcallbacks = arguments.callbacks
\n252: \t\t\t);
\n253:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:160)", + "codePrintPlain": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }\n159: \t\t// run it and get results\n160: \t\tvar results = runRaw( argumentCollection=arguments );\n161: \t\t// store latest results\n162: \t\tvariables.result = results;\n", + "column": 0, + "line": 160, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }
\n159: \t\t// run it and get results
\n160: \t\tvar results = runRaw( argumentCollection=arguments );
\n161: \t\t// store latest results
\n162: \t\tvariables.result = results;
\n" + }, { + "Raw_Trace": "system.runners.htmlrunner_cfm$cf.call(/testbox/system/runners/HTMLRunner.cfm:49)", + "codePrintPlain": "47: \n48: // Run Tests using correct reporter\n49: results = testbox.run( reporter=url.reporter );\n50: \n51: // Write TEST.properties in report destination path.\n", + "column": 0, + "line": 49, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/HTMLRunner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "47:
\n48: // Run Tests using correct reporter
\n49: results = testbox.run( reporter=url.reporter );
\n50:
\n51: // Write TEST.properties in report destination path.
\n" + }, { + "Raw_Trace": "tests.runner_cfm$cf.call(/tests/runner.cfm:21)", + "codePrintPlain": "19: \n20: \n21: \n", + "column": 0, + "line": 21, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/runner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "19:
\n20: <!--- Include the TestBox HTML Runner --->
\n21: <cfinclude template="/testbox/system/runners/HTMLRunner.cfm" >
\n" + }], + "ErrorCode": "0", + "type": "java.util.ConcurrentModificationException", + "StackTrace": "lucee.runtime.exp.NativeException: java.util.ConcurrentModificationException\n\tat java.util.HashMap$HashIterator.nextNode(HashMap.java:1442)\n\tat java.util.HashMap$KeyIterator.next(HashMap.java:1466)\n\tat io.undertow.servlet.util.IteratorEnumeration.nextElement(IteratorEnumeration.java:44)\n\tat lucee.runtime.type.scope.RequestImpl.clear(RequestImpl.java:157)\n\tat lucee.runtime.functions.struct.StructClear.call(StructClear.java:36)\n\tat specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall1(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc:18)\n\tat specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:695)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:2014)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.basespec_cfc$cf.udfCall5(/testbox/system/BaseSpec.cfc:1011)\n\tat system.basespec_cfc$cf.udfCall(/testbox/system/BaseSpec.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:695)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:2014)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:208)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:812)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:81)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.call(UDFImpl.java:226)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:693)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.call(ComponentImpl.java:1997)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithoutNamedValues(VariableUtilImpl.java:756)\n\tat lucee.runtime.PageContextImpl.getFunction(PageContextImpl.java:1718)\n\tat system.testbox_cfc$cf.udfCall2(/testbox/system/TestBox.cfc:478)\n\tat system.testbox_cfc$cf.udfCall(/testbox/system/TestBox.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:812)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:251)\n\tat system.testbox_cfc$cf.udfCall(/testbox/system/TestBox.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:812)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:160)\n\tat system.testbox_cfc$cf.udfCall(/testbox/system/TestBox.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:695)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:2014)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.runners.htmlrunner_cfm$cf.call(/testbox/system/runners/HTMLRunner.cfm:49)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:933)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:823)\n\tat lucee.runtime.PageContextImpl.doInclude(PageContextImpl.java:805)\n\tat tests.runner_cfm$cf.call(/tests/runner.cfm:21)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:933)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:823)\n\tat lucee.runtime.listener.ModernAppListener._onRequest(ModernAppListener.java:218)\n\tat lucee.runtime.listener.MixedAppListener.onRequest(MixedAppListener.java:43)\n\tat lucee.runtime.PageContextImpl.execute(PageContextImpl.java:2464)\n\tat lucee.runtime.PageContextImpl._execute(PageContextImpl.java:2454)\n\tat lucee.runtime.PageContextImpl.executeCFML(PageContextImpl.java:2427)\n\tat lucee.runtime.engine.Request.exe(Request.java:44)\n\tat lucee.runtime.engine.CFMLEngineImpl._service(CFMLEngineImpl.java:1090)\n\tat lucee.runtime.engine.CFMLEngineImpl.serviceCFML(CFMLEngineImpl.java:1038)\n\tat lucee.loader.engine.CFMLEngineWrapper.serviceCFML(CFMLEngineWrapper.java:102)\n\tat lucee.loader.servlet.CFMLServlet.service(CFMLServlet.java:51)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:790)\n\tat io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)\n\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)\n\tat org.cfmlprojects.regexpathinfofilter.RegexPathInfoFilter.doFilter(RegexPathInfoFilter.java:47)\n\tat io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)\n\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)\n\tat sun.reflect.GeneratedMethodAccessor53.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat com.intergral.fusionreactor.j2ee.filterchain.WrappedFilterChain.doFilter(WrappedFilterChain.java:134)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doNext(FusionReactorRequestHandler.java:764)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doHttpServletRequest(FusionReactorRequestHandler.java:344)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doFusionRequest(FusionReactorRequestHandler.java:207)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.handle(FusionReactorRequestHandler.java:801)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorCoreFilter.doFilter(FusionReactorCoreFilter.java:36)\n\tat sun.reflect.GeneratedMethodAccessor51.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat com.intergral.fusionreactor.j2ee.filterchain.WrappedFilterChain.doFilter(WrappedFilterChain.java:71)\n\tat sun.reflect.GeneratedMethodAccessor50.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat com.intergral.fusionreactor.agent.filter.FusionReactorStaticFilter.doFilter(FusionReactorStaticFilter.java:54)\n\tat com.intergral.fusionreactor.agent.pointcuts.NewFilterChainPointCut$1.invoke(NewFilterChainPointCut.java:41)\n\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java)\n\tat io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)\n\tat io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)\n\tat io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:64)\n\tat io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)\n\tat io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)\n\tat io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)\n\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n\tat io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)\n\tat io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)\n\tat io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)\n\tat io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)\n\tat io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)\n\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:81)\n\tat io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:138)\n\tat io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135)\n\tat io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)\n\tat io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81)\n\tat io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:104)\n\tat io.undertow.server.Connectors.executeRootHandler(Connectors.java:336)\n\tat io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\n\tat java.lang.Thread.run(Thread.java:748)\nCaused by: java.util.ConcurrentModificationException\n\t... 137 more\n", + "ExtendedInfo": "" + }, + "startTime": 1553009673137, + "totalDuration": 7, + "failOrigin": [{ + "Raw_Trace": "specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall1(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc:18)", + "codePrintPlain": "16: \n17: \tfunction teardown(){\n18: \t\tstructClear( request );\n19: \t}\n20: \n", + "column": 0, + "line": 18, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/specsWithFailures/MXUnitExpectedExceptions.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "16:
\n17: \tfunction teardown(){
\n18: \t\tstructClear( request );
\n19: \t}
\n20:
\n" + }, { + "Raw_Trace": "system.basespec_cfc$cf.udfCall5(/testbox/system/BaseSpec.cfc:1011)", + "codePrintPlain": "1009: \t\t\t\t\t// execute teardown()\n1010: \t\t\t\t\tif( structKeyExists( this, \"teardown\" ) ){ this.teardown( currentMethod=arguments.spec.name ); }\n1011: \t\t\t\t}\n1012: \n1013: \t\t\t\t// store spec status\n", + "column": 0, + "line": 1011, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/BaseSpec.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "1009: \t\t\t\t\t// execute teardown()
\n1010: \t\t\t\t\tif( structKeyExists( this, "teardown" ) ){ this.teardown( currentMethod=arguments.spec.name ); }
\n1011: \t\t\t\t}
\n1012:
\n1013: \t\t\t\t// store spec status
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:208)", + "codePrintPlain": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,\n207: \t\t\t\t\t\trunner=this\n208: \t\t\t\t\t);\n209: \n210: \t\t\t\t\t// verify call backs\n", + "column": 0, + "line": 208, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,
\n207: \t\t\t\t\t\trunner=this
\n208: \t\t\t\t\t);
\n209:
\n210: \t\t\t\t\t// verify call backs
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:81)", + "codePrintPlain": "79: \t\t\t\t\t\ttestResults=arguments.testResults,\n80: \t\t\t\t\t\tbundleStats=bundleStats,\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks\n82: \t\t\t\t\t);\n83: \n", + "column": 0, + "line": 81, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "79: \t\t\t\t\t\ttestResults=arguments.testResults,
\n80: \t\t\t\t\t\tbundleStats=bundleStats,
\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks
\n82: \t\t\t\t\t);
\n83:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall2(/testbox/system/TestBox.cfc:478)", + "codePrintPlain": "476: \t\t\t\t// Run via xUnit Style\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );\n479: \t\t\t}\n480: \t\t} catch( Any e ){\n", + "column": 0, + "line": 478, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "476: \t\t\t\t// Run via xUnit Style
\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )
\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );
\n479: \t\t\t}
\n480: \t\t} catch( Any e ){
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:251)", + "codePrintPlain": "249: \t\t\t\tbundlePath = thisBundlePath,\n250: \t\t\t\ttestResults = results,\n251: \t\t\t\tcallbacks = arguments.callbacks\n252: \t\t\t);\n253: \n", + "column": 0, + "line": 251, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "249: \t\t\t\tbundlePath = thisBundlePath,
\n250: \t\t\t\ttestResults = results,
\n251: \t\t\t\tcallbacks = arguments.callbacks
\n252: \t\t\t);
\n253:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:160)", + "codePrintPlain": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }\n159: \t\t// run it and get results\n160: \t\tvar results = runRaw( argumentCollection=arguments );\n161: \t\t// store latest results\n162: \t\tvariables.result = results;\n", + "column": 0, + "line": 160, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }
\n159: \t\t// run it and get results
\n160: \t\tvar results = runRaw( argumentCollection=arguments );
\n161: \t\t// store latest results
\n162: \t\tvariables.result = results;
\n" + }, { + "Raw_Trace": "system.runners.htmlrunner_cfm$cf.call(/testbox/system/runners/HTMLRunner.cfm:49)", + "codePrintPlain": "47: \n48: // Run Tests using correct reporter\n49: results = testbox.run( reporter=url.reporter );\n50: \n51: // Write TEST.properties in report destination path.\n", + "column": 0, + "line": 49, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/HTMLRunner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "47:
\n48: // Run Tests using correct reporter
\n49: results = testbox.run( reporter=url.reporter );
\n50:
\n51: // Write TEST.properties in report destination path.
\n" + }, { + "Raw_Trace": "tests.runner_cfm$cf.call(/tests/runner.cfm:21)", + "codePrintPlain": "19: \n20: \n21: \n", + "column": 0, + "line": 21, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/runner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "19:
\n20: <!--- Include the TestBox HTML Runner --->
\n21: <cfinclude template="/testbox/system/runners/HTMLRunner.cfm" >
\n" + }], + "status": "Error", + "suiteID": "0F9E64ECB27A3F1E33BC78FB7266E610", + "endTime": 1553009673144, + "name": "testExpectedExceptionNoValue", + "id": "EA719766B29000E8EB13E08618EB62D8", + "failMessage": "" + }, { + "error": { + "Extended_Info": "", + "Message": "java.util.ConcurrentModificationException", + "Detail": "", + "additional": {}, + "TagContext": [{ + "Raw_Trace": "specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall1(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc:18)", + "codePrintPlain": "16: \n17: \tfunction teardown(){\n18: \t\tstructClear( request );\n19: \t}\n20: \n", + "column": 0, + "line": 18, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/specsWithFailures/MXUnitExpectedExceptions.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "16:
\n17: \tfunction teardown(){
\n18: \t\tstructClear( request );
\n19: \t}
\n20:
\n" + }, { + "Raw_Trace": "system.basespec_cfc$cf.udfCall5(/testbox/system/BaseSpec.cfc:1011)", + "codePrintPlain": "1009: \t\t\t\t\t// execute teardown()\n1010: \t\t\t\t\tif( structKeyExists( this, \"teardown\" ) ){ this.teardown( currentMethod=arguments.spec.name ); }\n1011: \t\t\t\t}\n1012: \n1013: \t\t\t\t// store spec status\n", + "column": 0, + "line": 1011, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/BaseSpec.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "1009: \t\t\t\t\t// execute teardown()
\n1010: \t\t\t\t\tif( structKeyExists( this, "teardown" ) ){ this.teardown( currentMethod=arguments.spec.name ); }
\n1011: \t\t\t\t}
\n1012:
\n1013: \t\t\t\t// store spec status
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:208)", + "codePrintPlain": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,\n207: \t\t\t\t\t\trunner=this\n208: \t\t\t\t\t);\n209: \n210: \t\t\t\t\t// verify call backs\n", + "column": 0, + "line": 208, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,
\n207: \t\t\t\t\t\trunner=this
\n208: \t\t\t\t\t);
\n209:
\n210: \t\t\t\t\t// verify call backs
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:81)", + "codePrintPlain": "79: \t\t\t\t\t\ttestResults=arguments.testResults,\n80: \t\t\t\t\t\tbundleStats=bundleStats,\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks\n82: \t\t\t\t\t);\n83: \n", + "column": 0, + "line": 81, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "79: \t\t\t\t\t\ttestResults=arguments.testResults,
\n80: \t\t\t\t\t\tbundleStats=bundleStats,
\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks
\n82: \t\t\t\t\t);
\n83:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall2(/testbox/system/TestBox.cfc:478)", + "codePrintPlain": "476: \t\t\t\t// Run via xUnit Style\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );\n479: \t\t\t}\n480: \t\t} catch( Any e ){\n", + "column": 0, + "line": 478, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "476: \t\t\t\t// Run via xUnit Style
\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )
\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );
\n479: \t\t\t}
\n480: \t\t} catch( Any e ){
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:251)", + "codePrintPlain": "249: \t\t\t\tbundlePath = thisBundlePath,\n250: \t\t\t\ttestResults = results,\n251: \t\t\t\tcallbacks = arguments.callbacks\n252: \t\t\t);\n253: \n", + "column": 0, + "line": 251, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "249: \t\t\t\tbundlePath = thisBundlePath,
\n250: \t\t\t\ttestResults = results,
\n251: \t\t\t\tcallbacks = arguments.callbacks
\n252: \t\t\t);
\n253:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:160)", + "codePrintPlain": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }\n159: \t\t// run it and get results\n160: \t\tvar results = runRaw( argumentCollection=arguments );\n161: \t\t// store latest results\n162: \t\tvariables.result = results;\n", + "column": 0, + "line": 160, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }
\n159: \t\t// run it and get results
\n160: \t\tvar results = runRaw( argumentCollection=arguments );
\n161: \t\t// store latest results
\n162: \t\tvariables.result = results;
\n" + }, { + "Raw_Trace": "system.runners.htmlrunner_cfm$cf.call(/testbox/system/runners/HTMLRunner.cfm:49)", + "codePrintPlain": "47: \n48: // Run Tests using correct reporter\n49: results = testbox.run( reporter=url.reporter );\n50: \n51: // Write TEST.properties in report destination path.\n", + "column": 0, + "line": 49, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/HTMLRunner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "47:
\n48: // Run Tests using correct reporter
\n49: results = testbox.run( reporter=url.reporter );
\n50:
\n51: // Write TEST.properties in report destination path.
\n" + }, { + "Raw_Trace": "tests.runner_cfm$cf.call(/tests/runner.cfm:21)", + "codePrintPlain": "19: \n20: \n21: \n", + "column": 0, + "line": 21, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/runner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "19:
\n20: <!--- Include the TestBox HTML Runner --->
\n21: <cfinclude template="/testbox/system/runners/HTMLRunner.cfm" >
\n" + }], + "ErrorCode": "0", + "type": "java.util.ConcurrentModificationException", + "StackTrace": "lucee.runtime.exp.NativeException: java.util.ConcurrentModificationException\n\tat java.util.HashMap$HashIterator.nextNode(HashMap.java:1442)\n\tat java.util.HashMap$KeyIterator.next(HashMap.java:1466)\n\tat io.undertow.servlet.util.IteratorEnumeration.nextElement(IteratorEnumeration.java:44)\n\tat lucee.runtime.type.scope.RequestImpl.clear(RequestImpl.java:157)\n\tat lucee.runtime.functions.struct.StructClear.call(StructClear.java:36)\n\tat specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall1(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc:18)\n\tat specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:695)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:2014)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.basespec_cfc$cf.udfCall5(/testbox/system/BaseSpec.cfc:1011)\n\tat system.basespec_cfc$cf.udfCall(/testbox/system/BaseSpec.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:695)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:2014)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:208)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:812)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:81)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.call(UDFImpl.java:226)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:693)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.call(ComponentImpl.java:1997)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithoutNamedValues(VariableUtilImpl.java:756)\n\tat lucee.runtime.PageContextImpl.getFunction(PageContextImpl.java:1718)\n\tat system.testbox_cfc$cf.udfCall2(/testbox/system/TestBox.cfc:478)\n\tat system.testbox_cfc$cf.udfCall(/testbox/system/TestBox.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:812)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:251)\n\tat system.testbox_cfc$cf.udfCall(/testbox/system/TestBox.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:812)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:160)\n\tat system.testbox_cfc$cf.udfCall(/testbox/system/TestBox.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:695)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:2014)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.runners.htmlrunner_cfm$cf.call(/testbox/system/runners/HTMLRunner.cfm:49)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:933)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:823)\n\tat lucee.runtime.PageContextImpl.doInclude(PageContextImpl.java:805)\n\tat tests.runner_cfm$cf.call(/tests/runner.cfm:21)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:933)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:823)\n\tat lucee.runtime.listener.ModernAppListener._onRequest(ModernAppListener.java:218)\n\tat lucee.runtime.listener.MixedAppListener.onRequest(MixedAppListener.java:43)\n\tat lucee.runtime.PageContextImpl.execute(PageContextImpl.java:2464)\n\tat lucee.runtime.PageContextImpl._execute(PageContextImpl.java:2454)\n\tat lucee.runtime.PageContextImpl.executeCFML(PageContextImpl.java:2427)\n\tat lucee.runtime.engine.Request.exe(Request.java:44)\n\tat lucee.runtime.engine.CFMLEngineImpl._service(CFMLEngineImpl.java:1090)\n\tat lucee.runtime.engine.CFMLEngineImpl.serviceCFML(CFMLEngineImpl.java:1038)\n\tat lucee.loader.engine.CFMLEngineWrapper.serviceCFML(CFMLEngineWrapper.java:102)\n\tat lucee.loader.servlet.CFMLServlet.service(CFMLServlet.java:51)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:790)\n\tat io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)\n\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)\n\tat org.cfmlprojects.regexpathinfofilter.RegexPathInfoFilter.doFilter(RegexPathInfoFilter.java:47)\n\tat io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)\n\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)\n\tat sun.reflect.GeneratedMethodAccessor53.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat com.intergral.fusionreactor.j2ee.filterchain.WrappedFilterChain.doFilter(WrappedFilterChain.java:134)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doNext(FusionReactorRequestHandler.java:764)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doHttpServletRequest(FusionReactorRequestHandler.java:344)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doFusionRequest(FusionReactorRequestHandler.java:207)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.handle(FusionReactorRequestHandler.java:801)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorCoreFilter.doFilter(FusionReactorCoreFilter.java:36)\n\tat sun.reflect.GeneratedMethodAccessor51.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat com.intergral.fusionreactor.j2ee.filterchain.WrappedFilterChain.doFilter(WrappedFilterChain.java:71)\n\tat sun.reflect.GeneratedMethodAccessor50.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat com.intergral.fusionreactor.agent.filter.FusionReactorStaticFilter.doFilter(FusionReactorStaticFilter.java:54)\n\tat com.intergral.fusionreactor.agent.pointcuts.NewFilterChainPointCut$1.invoke(NewFilterChainPointCut.java:41)\n\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java)\n\tat io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)\n\tat io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)\n\tat io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:64)\n\tat io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)\n\tat io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)\n\tat io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)\n\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n\tat io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)\n\tat io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)\n\tat io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)\n\tat io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)\n\tat io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)\n\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:81)\n\tat io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:138)\n\tat io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135)\n\tat io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)\n\tat io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81)\n\tat io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:104)\n\tat io.undertow.server.Connectors.executeRootHandler(Connectors.java:336)\n\tat io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\n\tat java.lang.Thread.run(Thread.java:748)\nCaused by: java.util.ConcurrentModificationException\n\t... 137 more\n", + "ExtendedInfo": "" + }, + "startTime": 1553009673144, + "totalDuration": 4, + "failOrigin": [{ + "Raw_Trace": "specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall1(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc:18)", + "codePrintPlain": "16: \n17: \tfunction teardown(){\n18: \t\tstructClear( request );\n19: \t}\n20: \n", + "column": 0, + "line": 18, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/specsWithFailures/MXUnitExpectedExceptions.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "16:
\n17: \tfunction teardown(){
\n18: \t\tstructClear( request );
\n19: \t}
\n20:
\n" + }, { + "Raw_Trace": "system.basespec_cfc$cf.udfCall5(/testbox/system/BaseSpec.cfc:1011)", + "codePrintPlain": "1009: \t\t\t\t\t// execute teardown()\n1010: \t\t\t\t\tif( structKeyExists( this, \"teardown\" ) ){ this.teardown( currentMethod=arguments.spec.name ); }\n1011: \t\t\t\t}\n1012: \n1013: \t\t\t\t// store spec status\n", + "column": 0, + "line": 1011, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/BaseSpec.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "1009: \t\t\t\t\t// execute teardown()
\n1010: \t\t\t\t\tif( structKeyExists( this, "teardown" ) ){ this.teardown( currentMethod=arguments.spec.name ); }
\n1011: \t\t\t\t}
\n1012:
\n1013: \t\t\t\t// store spec status
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:208)", + "codePrintPlain": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,\n207: \t\t\t\t\t\trunner=this\n208: \t\t\t\t\t);\n209: \n210: \t\t\t\t\t// verify call backs\n", + "column": 0, + "line": 208, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,
\n207: \t\t\t\t\t\trunner=this
\n208: \t\t\t\t\t);
\n209:
\n210: \t\t\t\t\t// verify call backs
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:81)", + "codePrintPlain": "79: \t\t\t\t\t\ttestResults=arguments.testResults,\n80: \t\t\t\t\t\tbundleStats=bundleStats,\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks\n82: \t\t\t\t\t);\n83: \n", + "column": 0, + "line": 81, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "79: \t\t\t\t\t\ttestResults=arguments.testResults,
\n80: \t\t\t\t\t\tbundleStats=bundleStats,
\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks
\n82: \t\t\t\t\t);
\n83:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall2(/testbox/system/TestBox.cfc:478)", + "codePrintPlain": "476: \t\t\t\t// Run via xUnit Style\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );\n479: \t\t\t}\n480: \t\t} catch( Any e ){\n", + "column": 0, + "line": 478, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "476: \t\t\t\t// Run via xUnit Style
\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )
\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );
\n479: \t\t\t}
\n480: \t\t} catch( Any e ){
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:251)", + "codePrintPlain": "249: \t\t\t\tbundlePath = thisBundlePath,\n250: \t\t\t\ttestResults = results,\n251: \t\t\t\tcallbacks = arguments.callbacks\n252: \t\t\t);\n253: \n", + "column": 0, + "line": 251, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "249: \t\t\t\tbundlePath = thisBundlePath,
\n250: \t\t\t\ttestResults = results,
\n251: \t\t\t\tcallbacks = arguments.callbacks
\n252: \t\t\t);
\n253:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:160)", + "codePrintPlain": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }\n159: \t\t// run it and get results\n160: \t\tvar results = runRaw( argumentCollection=arguments );\n161: \t\t// store latest results\n162: \t\tvariables.result = results;\n", + "column": 0, + "line": 160, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }
\n159: \t\t// run it and get results
\n160: \t\tvar results = runRaw( argumentCollection=arguments );
\n161: \t\t// store latest results
\n162: \t\tvariables.result = results;
\n" + }, { + "Raw_Trace": "system.runners.htmlrunner_cfm$cf.call(/testbox/system/runners/HTMLRunner.cfm:49)", + "codePrintPlain": "47: \n48: // Run Tests using correct reporter\n49: results = testbox.run( reporter=url.reporter );\n50: \n51: // Write TEST.properties in report destination path.\n", + "column": 0, + "line": 49, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/HTMLRunner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "47:
\n48: // Run Tests using correct reporter
\n49: results = testbox.run( reporter=url.reporter );
\n50:
\n51: // Write TEST.properties in report destination path.
\n" + }, { + "Raw_Trace": "tests.runner_cfm$cf.call(/tests/runner.cfm:21)", + "codePrintPlain": "19: \n20: \n21: \n", + "column": 0, + "line": 21, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/runner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "19:
\n20: <!--- Include the TestBox HTML Runner --->
\n21: <cfinclude template="/testbox/system/runners/HTMLRunner.cfm" >
\n" + }], + "status": "Error", + "suiteID": "0F9E64ECB27A3F1E33BC78FB7266E610", + "endTime": 1553009673148, + "name": "testExpectedExceptionFromMethodWithType", + "id": "DEA4D1B7E4D5685C8F5E56FC2EF4B07F", + "failMessage": "" + }, { + "error": { + "Extended_Info": "", + "Message": "java.util.ConcurrentModificationException", + "Detail": "", + "additional": {}, + "TagContext": [{ + "Raw_Trace": "specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall1(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc:18)", + "codePrintPlain": "16: \n17: \tfunction teardown(){\n18: \t\tstructClear( request );\n19: \t}\n20: \n", + "column": 0, + "line": 18, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/specsWithFailures/MXUnitExpectedExceptions.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "16:
\n17: \tfunction teardown(){
\n18: \t\tstructClear( request );
\n19: \t}
\n20:
\n" + }, { + "Raw_Trace": "system.basespec_cfc$cf.udfCall5(/testbox/system/BaseSpec.cfc:1011)", + "codePrintPlain": "1009: \t\t\t\t\t// execute teardown()\n1010: \t\t\t\t\tif( structKeyExists( this, \"teardown\" ) ){ this.teardown( currentMethod=arguments.spec.name ); }\n1011: \t\t\t\t}\n1012: \n1013: \t\t\t\t// store spec status\n", + "column": 0, + "line": 1011, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/BaseSpec.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "1009: \t\t\t\t\t// execute teardown()
\n1010: \t\t\t\t\tif( structKeyExists( this, "teardown" ) ){ this.teardown( currentMethod=arguments.spec.name ); }
\n1011: \t\t\t\t}
\n1012:
\n1013: \t\t\t\t// store spec status
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:208)", + "codePrintPlain": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,\n207: \t\t\t\t\t\trunner=this\n208: \t\t\t\t\t);\n209: \n210: \t\t\t\t\t// verify call backs\n", + "column": 0, + "line": 208, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,
\n207: \t\t\t\t\t\trunner=this
\n208: \t\t\t\t\t);
\n209:
\n210: \t\t\t\t\t// verify call backs
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:81)", + "codePrintPlain": "79: \t\t\t\t\t\ttestResults=arguments.testResults,\n80: \t\t\t\t\t\tbundleStats=bundleStats,\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks\n82: \t\t\t\t\t);\n83: \n", + "column": 0, + "line": 81, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "79: \t\t\t\t\t\ttestResults=arguments.testResults,
\n80: \t\t\t\t\t\tbundleStats=bundleStats,
\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks
\n82: \t\t\t\t\t);
\n83:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall2(/testbox/system/TestBox.cfc:478)", + "codePrintPlain": "476: \t\t\t\t// Run via xUnit Style\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );\n479: \t\t\t}\n480: \t\t} catch( Any e ){\n", + "column": 0, + "line": 478, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "476: \t\t\t\t// Run via xUnit Style
\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )
\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );
\n479: \t\t\t}
\n480: \t\t} catch( Any e ){
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:251)", + "codePrintPlain": "249: \t\t\t\tbundlePath = thisBundlePath,\n250: \t\t\t\ttestResults = results,\n251: \t\t\t\tcallbacks = arguments.callbacks\n252: \t\t\t);\n253: \n", + "column": 0, + "line": 251, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "249: \t\t\t\tbundlePath = thisBundlePath,
\n250: \t\t\t\ttestResults = results,
\n251: \t\t\t\tcallbacks = arguments.callbacks
\n252: \t\t\t);
\n253:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:160)", + "codePrintPlain": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }\n159: \t\t// run it and get results\n160: \t\tvar results = runRaw( argumentCollection=arguments );\n161: \t\t// store latest results\n162: \t\tvariables.result = results;\n", + "column": 0, + "line": 160, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }
\n159: \t\t// run it and get results
\n160: \t\tvar results = runRaw( argumentCollection=arguments );
\n161: \t\t// store latest results
\n162: \t\tvariables.result = results;
\n" + }, { + "Raw_Trace": "system.runners.htmlrunner_cfm$cf.call(/testbox/system/runners/HTMLRunner.cfm:49)", + "codePrintPlain": "47: \n48: // Run Tests using correct reporter\n49: results = testbox.run( reporter=url.reporter );\n50: \n51: // Write TEST.properties in report destination path.\n", + "column": 0, + "line": 49, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/HTMLRunner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "47:
\n48: // Run Tests using correct reporter
\n49: results = testbox.run( reporter=url.reporter );
\n50:
\n51: // Write TEST.properties in report destination path.
\n" + }, { + "Raw_Trace": "tests.runner_cfm$cf.call(/tests/runner.cfm:21)", + "codePrintPlain": "19: \n20: \n21: \n", + "column": 0, + "line": 21, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/runner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "19:
\n20: <!--- Include the TestBox HTML Runner --->
\n21: <cfinclude template="/testbox/system/runners/HTMLRunner.cfm" >
\n" + }], + "ErrorCode": "0", + "type": "java.util.ConcurrentModificationException", + "StackTrace": "lucee.runtime.exp.NativeException: java.util.ConcurrentModificationException\n\tat java.util.HashMap$HashIterator.nextNode(HashMap.java:1442)\n\tat java.util.HashMap$KeyIterator.next(HashMap.java:1466)\n\tat io.undertow.servlet.util.IteratorEnumeration.nextElement(IteratorEnumeration.java:44)\n\tat lucee.runtime.type.scope.RequestImpl.clear(RequestImpl.java:157)\n\tat lucee.runtime.functions.struct.StructClear.call(StructClear.java:36)\n\tat specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall1(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc:18)\n\tat specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:695)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:2014)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.basespec_cfc$cf.udfCall5(/testbox/system/BaseSpec.cfc:1011)\n\tat system.basespec_cfc$cf.udfCall(/testbox/system/BaseSpec.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:695)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:2014)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:208)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:812)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:81)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.call(UDFImpl.java:226)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:693)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.call(ComponentImpl.java:1997)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithoutNamedValues(VariableUtilImpl.java:756)\n\tat lucee.runtime.PageContextImpl.getFunction(PageContextImpl.java:1718)\n\tat system.testbox_cfc$cf.udfCall2(/testbox/system/TestBox.cfc:478)\n\tat system.testbox_cfc$cf.udfCall(/testbox/system/TestBox.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:812)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:251)\n\tat system.testbox_cfc$cf.udfCall(/testbox/system/TestBox.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:812)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:160)\n\tat system.testbox_cfc$cf.udfCall(/testbox/system/TestBox.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:695)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:2014)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.runners.htmlrunner_cfm$cf.call(/testbox/system/runners/HTMLRunner.cfm:49)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:933)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:823)\n\tat lucee.runtime.PageContextImpl.doInclude(PageContextImpl.java:805)\n\tat tests.runner_cfm$cf.call(/tests/runner.cfm:21)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:933)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:823)\n\tat lucee.runtime.listener.ModernAppListener._onRequest(ModernAppListener.java:218)\n\tat lucee.runtime.listener.MixedAppListener.onRequest(MixedAppListener.java:43)\n\tat lucee.runtime.PageContextImpl.execute(PageContextImpl.java:2464)\n\tat lucee.runtime.PageContextImpl._execute(PageContextImpl.java:2454)\n\tat lucee.runtime.PageContextImpl.executeCFML(PageContextImpl.java:2427)\n\tat lucee.runtime.engine.Request.exe(Request.java:44)\n\tat lucee.runtime.engine.CFMLEngineImpl._service(CFMLEngineImpl.java:1090)\n\tat lucee.runtime.engine.CFMLEngineImpl.serviceCFML(CFMLEngineImpl.java:1038)\n\tat lucee.loader.engine.CFMLEngineWrapper.serviceCFML(CFMLEngineWrapper.java:102)\n\tat lucee.loader.servlet.CFMLServlet.service(CFMLServlet.java:51)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:790)\n\tat io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)\n\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)\n\tat org.cfmlprojects.regexpathinfofilter.RegexPathInfoFilter.doFilter(RegexPathInfoFilter.java:47)\n\tat io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)\n\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)\n\tat sun.reflect.GeneratedMethodAccessor53.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat com.intergral.fusionreactor.j2ee.filterchain.WrappedFilterChain.doFilter(WrappedFilterChain.java:134)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doNext(FusionReactorRequestHandler.java:764)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doHttpServletRequest(FusionReactorRequestHandler.java:344)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doFusionRequest(FusionReactorRequestHandler.java:207)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.handle(FusionReactorRequestHandler.java:801)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorCoreFilter.doFilter(FusionReactorCoreFilter.java:36)\n\tat sun.reflect.GeneratedMethodAccessor51.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat com.intergral.fusionreactor.j2ee.filterchain.WrappedFilterChain.doFilter(WrappedFilterChain.java:71)\n\tat sun.reflect.GeneratedMethodAccessor50.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat com.intergral.fusionreactor.agent.filter.FusionReactorStaticFilter.doFilter(FusionReactorStaticFilter.java:54)\n\tat com.intergral.fusionreactor.agent.pointcuts.NewFilterChainPointCut$1.invoke(NewFilterChainPointCut.java:41)\n\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java)\n\tat io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)\n\tat io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)\n\tat io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:64)\n\tat io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)\n\tat io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)\n\tat io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)\n\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n\tat io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)\n\tat io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)\n\tat io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)\n\tat io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)\n\tat io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)\n\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:81)\n\tat io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:138)\n\tat io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135)\n\tat io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)\n\tat io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81)\n\tat io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:104)\n\tat io.undertow.server.Connectors.executeRootHandler(Connectors.java:336)\n\tat io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\n\tat java.lang.Thread.run(Thread.java:748)\nCaused by: java.util.ConcurrentModificationException\n\t... 137 more\n", + "ExtendedInfo": "" + }, + "startTime": 1553009673148, + "totalDuration": 4, + "failOrigin": [{ + "Raw_Trace": "specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall1(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc:18)", + "codePrintPlain": "16: \n17: \tfunction teardown(){\n18: \t\tstructClear( request );\n19: \t}\n20: \n", + "column": 0, + "line": 18, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/specsWithFailures/MXUnitExpectedExceptions.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "16:
\n17: \tfunction teardown(){
\n18: \t\tstructClear( request );
\n19: \t}
\n20:
\n" + }, { + "Raw_Trace": "system.basespec_cfc$cf.udfCall5(/testbox/system/BaseSpec.cfc:1011)", + "codePrintPlain": "1009: \t\t\t\t\t// execute teardown()\n1010: \t\t\t\t\tif( structKeyExists( this, \"teardown\" ) ){ this.teardown( currentMethod=arguments.spec.name ); }\n1011: \t\t\t\t}\n1012: \n1013: \t\t\t\t// store spec status\n", + "column": 0, + "line": 1011, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/BaseSpec.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "1009: \t\t\t\t\t// execute teardown()
\n1010: \t\t\t\t\tif( structKeyExists( this, "teardown" ) ){ this.teardown( currentMethod=arguments.spec.name ); }
\n1011: \t\t\t\t}
\n1012:
\n1013: \t\t\t\t// store spec status
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:208)", + "codePrintPlain": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,\n207: \t\t\t\t\t\trunner=this\n208: \t\t\t\t\t);\n209: \n210: \t\t\t\t\t// verify call backs\n", + "column": 0, + "line": 208, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,
\n207: \t\t\t\t\t\trunner=this
\n208: \t\t\t\t\t);
\n209:
\n210: \t\t\t\t\t// verify call backs
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:81)", + "codePrintPlain": "79: \t\t\t\t\t\ttestResults=arguments.testResults,\n80: \t\t\t\t\t\tbundleStats=bundleStats,\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks\n82: \t\t\t\t\t);\n83: \n", + "column": 0, + "line": 81, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "79: \t\t\t\t\t\ttestResults=arguments.testResults,
\n80: \t\t\t\t\t\tbundleStats=bundleStats,
\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks
\n82: \t\t\t\t\t);
\n83:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall2(/testbox/system/TestBox.cfc:478)", + "codePrintPlain": "476: \t\t\t\t// Run via xUnit Style\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );\n479: \t\t\t}\n480: \t\t} catch( Any e ){\n", + "column": 0, + "line": 478, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "476: \t\t\t\t// Run via xUnit Style
\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )
\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );
\n479: \t\t\t}
\n480: \t\t} catch( Any e ){
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:251)", + "codePrintPlain": "249: \t\t\t\tbundlePath = thisBundlePath,\n250: \t\t\t\ttestResults = results,\n251: \t\t\t\tcallbacks = arguments.callbacks\n252: \t\t\t);\n253: \n", + "column": 0, + "line": 251, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "249: \t\t\t\tbundlePath = thisBundlePath,
\n250: \t\t\t\ttestResults = results,
\n251: \t\t\t\tcallbacks = arguments.callbacks
\n252: \t\t\t);
\n253:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:160)", + "codePrintPlain": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }\n159: \t\t// run it and get results\n160: \t\tvar results = runRaw( argumentCollection=arguments );\n161: \t\t// store latest results\n162: \t\tvariables.result = results;\n", + "column": 0, + "line": 160, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }
\n159: \t\t// run it and get results
\n160: \t\tvar results = runRaw( argumentCollection=arguments );
\n161: \t\t// store latest results
\n162: \t\tvariables.result = results;
\n" + }, { + "Raw_Trace": "system.runners.htmlrunner_cfm$cf.call(/testbox/system/runners/HTMLRunner.cfm:49)", + "codePrintPlain": "47: \n48: // Run Tests using correct reporter\n49: results = testbox.run( reporter=url.reporter );\n50: \n51: // Write TEST.properties in report destination path.\n", + "column": 0, + "line": 49, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/HTMLRunner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "47:
\n48: // Run Tests using correct reporter
\n49: results = testbox.run( reporter=url.reporter );
\n50:
\n51: // Write TEST.properties in report destination path.
\n" + }, { + "Raw_Trace": "tests.runner_cfm$cf.call(/tests/runner.cfm:21)", + "codePrintPlain": "19: \n20: \n21: \n", + "column": 0, + "line": 21, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/runner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "19:
\n20: <!--- Include the TestBox HTML Runner --->
\n21: <cfinclude template="/testbox/system/runners/HTMLRunner.cfm" >
\n" + }], + "status": "Error", + "suiteID": "0F9E64ECB27A3F1E33BC78FB7266E610", + "endTime": 1553009673152, + "name": "testRaiseException_pass", + "id": "DB1CDA5E9576C02CE49C6A3009AC84EA", + "failMessage": "" + }, { + "error": { + "Extended_Info": "", + "Message": "java.util.ConcurrentModificationException", + "Detail": "", + "additional": {}, + "TagContext": [{ + "Raw_Trace": "specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall1(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc:18)", + "codePrintPlain": "16: \n17: \tfunction teardown(){\n18: \t\tstructClear( request );\n19: \t}\n20: \n", + "column": 0, + "line": 18, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/specsWithFailures/MXUnitExpectedExceptions.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "16:
\n17: \tfunction teardown(){
\n18: \t\tstructClear( request );
\n19: \t}
\n20:
\n" + }, { + "Raw_Trace": "system.basespec_cfc$cf.udfCall5(/testbox/system/BaseSpec.cfc:1011)", + "codePrintPlain": "1009: \t\t\t\t\t// execute teardown()\n1010: \t\t\t\t\tif( structKeyExists( this, \"teardown\" ) ){ this.teardown( currentMethod=arguments.spec.name ); }\n1011: \t\t\t\t}\n1012: \n1013: \t\t\t\t// store spec status\n", + "column": 0, + "line": 1011, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/BaseSpec.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "1009: \t\t\t\t\t// execute teardown()
\n1010: \t\t\t\t\tif( structKeyExists( this, "teardown" ) ){ this.teardown( currentMethod=arguments.spec.name ); }
\n1011: \t\t\t\t}
\n1012:
\n1013: \t\t\t\t// store spec status
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:208)", + "codePrintPlain": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,\n207: \t\t\t\t\t\trunner=this\n208: \t\t\t\t\t);\n209: \n210: \t\t\t\t\t// verify call backs\n", + "column": 0, + "line": 208, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,
\n207: \t\t\t\t\t\trunner=this
\n208: \t\t\t\t\t);
\n209:
\n210: \t\t\t\t\t// verify call backs
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:81)", + "codePrintPlain": "79: \t\t\t\t\t\ttestResults=arguments.testResults,\n80: \t\t\t\t\t\tbundleStats=bundleStats,\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks\n82: \t\t\t\t\t);\n83: \n", + "column": 0, + "line": 81, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "79: \t\t\t\t\t\ttestResults=arguments.testResults,
\n80: \t\t\t\t\t\tbundleStats=bundleStats,
\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks
\n82: \t\t\t\t\t);
\n83:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall2(/testbox/system/TestBox.cfc:478)", + "codePrintPlain": "476: \t\t\t\t// Run via xUnit Style\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );\n479: \t\t\t}\n480: \t\t} catch( Any e ){\n", + "column": 0, + "line": 478, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "476: \t\t\t\t// Run via xUnit Style
\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )
\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );
\n479: \t\t\t}
\n480: \t\t} catch( Any e ){
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:251)", + "codePrintPlain": "249: \t\t\t\tbundlePath = thisBundlePath,\n250: \t\t\t\ttestResults = results,\n251: \t\t\t\tcallbacks = arguments.callbacks\n252: \t\t\t);\n253: \n", + "column": 0, + "line": 251, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "249: \t\t\t\tbundlePath = thisBundlePath,
\n250: \t\t\t\ttestResults = results,
\n251: \t\t\t\tcallbacks = arguments.callbacks
\n252: \t\t\t);
\n253:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:160)", + "codePrintPlain": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }\n159: \t\t// run it and get results\n160: \t\tvar results = runRaw( argumentCollection=arguments );\n161: \t\t// store latest results\n162: \t\tvariables.result = results;\n", + "column": 0, + "line": 160, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }
\n159: \t\t// run it and get results
\n160: \t\tvar results = runRaw( argumentCollection=arguments );
\n161: \t\t// store latest results
\n162: \t\tvariables.result = results;
\n" + }, { + "Raw_Trace": "system.runners.htmlrunner_cfm$cf.call(/testbox/system/runners/HTMLRunner.cfm:49)", + "codePrintPlain": "47: \n48: // Run Tests using correct reporter\n49: results = testbox.run( reporter=url.reporter );\n50: \n51: // Write TEST.properties in report destination path.\n", + "column": 0, + "line": 49, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/HTMLRunner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "47:
\n48: // Run Tests using correct reporter
\n49: results = testbox.run( reporter=url.reporter );
\n50:
\n51: // Write TEST.properties in report destination path.
\n" + }, { + "Raw_Trace": "tests.runner_cfm$cf.call(/tests/runner.cfm:21)", + "codePrintPlain": "19: \n20: \n21: \n", + "column": 0, + "line": 21, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/runner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "19:
\n20: <!--- Include the TestBox HTML Runner --->
\n21: <cfinclude template="/testbox/system/runners/HTMLRunner.cfm" >
\n" + }], + "ErrorCode": "0", + "type": "java.util.ConcurrentModificationException", + "StackTrace": "lucee.runtime.exp.NativeException: java.util.ConcurrentModificationException\n\tat java.util.HashMap$HashIterator.nextNode(HashMap.java:1442)\n\tat java.util.HashMap$KeyIterator.next(HashMap.java:1466)\n\tat io.undertow.servlet.util.IteratorEnumeration.nextElement(IteratorEnumeration.java:44)\n\tat lucee.runtime.type.scope.RequestImpl.clear(RequestImpl.java:157)\n\tat lucee.runtime.functions.struct.StructClear.call(StructClear.java:36)\n\tat specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall1(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc:18)\n\tat specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:695)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:2014)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.basespec_cfc$cf.udfCall5(/testbox/system/BaseSpec.cfc:1011)\n\tat system.basespec_cfc$cf.udfCall(/testbox/system/BaseSpec.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:695)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:2014)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:208)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:812)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:81)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.call(UDFImpl.java:226)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:693)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.call(ComponentImpl.java:1997)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithoutNamedValues(VariableUtilImpl.java:756)\n\tat lucee.runtime.PageContextImpl.getFunction(PageContextImpl.java:1718)\n\tat system.testbox_cfc$cf.udfCall2(/testbox/system/TestBox.cfc:478)\n\tat system.testbox_cfc$cf.udfCall(/testbox/system/TestBox.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:812)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:251)\n\tat system.testbox_cfc$cf.udfCall(/testbox/system/TestBox.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:812)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:160)\n\tat system.testbox_cfc$cf.udfCall(/testbox/system/TestBox.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:695)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:2014)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.runners.htmlrunner_cfm$cf.call(/testbox/system/runners/HTMLRunner.cfm:49)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:933)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:823)\n\tat lucee.runtime.PageContextImpl.doInclude(PageContextImpl.java:805)\n\tat tests.runner_cfm$cf.call(/tests/runner.cfm:21)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:933)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:823)\n\tat lucee.runtime.listener.ModernAppListener._onRequest(ModernAppListener.java:218)\n\tat lucee.runtime.listener.MixedAppListener.onRequest(MixedAppListener.java:43)\n\tat lucee.runtime.PageContextImpl.execute(PageContextImpl.java:2464)\n\tat lucee.runtime.PageContextImpl._execute(PageContextImpl.java:2454)\n\tat lucee.runtime.PageContextImpl.executeCFML(PageContextImpl.java:2427)\n\tat lucee.runtime.engine.Request.exe(Request.java:44)\n\tat lucee.runtime.engine.CFMLEngineImpl._service(CFMLEngineImpl.java:1090)\n\tat lucee.runtime.engine.CFMLEngineImpl.serviceCFML(CFMLEngineImpl.java:1038)\n\tat lucee.loader.engine.CFMLEngineWrapper.serviceCFML(CFMLEngineWrapper.java:102)\n\tat lucee.loader.servlet.CFMLServlet.service(CFMLServlet.java:51)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:790)\n\tat io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)\n\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)\n\tat org.cfmlprojects.regexpathinfofilter.RegexPathInfoFilter.doFilter(RegexPathInfoFilter.java:47)\n\tat io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)\n\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)\n\tat sun.reflect.GeneratedMethodAccessor53.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat com.intergral.fusionreactor.j2ee.filterchain.WrappedFilterChain.doFilter(WrappedFilterChain.java:134)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doNext(FusionReactorRequestHandler.java:764)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doHttpServletRequest(FusionReactorRequestHandler.java:344)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doFusionRequest(FusionReactorRequestHandler.java:207)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.handle(FusionReactorRequestHandler.java:801)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorCoreFilter.doFilter(FusionReactorCoreFilter.java:36)\n\tat sun.reflect.GeneratedMethodAccessor51.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat com.intergral.fusionreactor.j2ee.filterchain.WrappedFilterChain.doFilter(WrappedFilterChain.java:71)\n\tat sun.reflect.GeneratedMethodAccessor50.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat com.intergral.fusionreactor.agent.filter.FusionReactorStaticFilter.doFilter(FusionReactorStaticFilter.java:54)\n\tat com.intergral.fusionreactor.agent.pointcuts.NewFilterChainPointCut$1.invoke(NewFilterChainPointCut.java:41)\n\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java)\n\tat io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)\n\tat io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)\n\tat io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:64)\n\tat io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)\n\tat io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)\n\tat io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)\n\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n\tat io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)\n\tat io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)\n\tat io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)\n\tat io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)\n\tat io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)\n\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:81)\n\tat io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:138)\n\tat io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135)\n\tat io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)\n\tat io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81)\n\tat io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:104)\n\tat io.undertow.server.Connectors.executeRootHandler(Connectors.java:336)\n\tat io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\n\tat java.lang.Thread.run(Thread.java:748)\nCaused by: java.util.ConcurrentModificationException\n\t... 137 more\n", + "ExtendedInfo": "" + }, + "startTime": 1553009673152, + "totalDuration": 4, + "failOrigin": [{ + "Raw_Trace": "specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall1(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc:18)", + "codePrintPlain": "16: \n17: \tfunction teardown(){\n18: \t\tstructClear( request );\n19: \t}\n20: \n", + "column": 0, + "line": 18, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/specsWithFailures/MXUnitExpectedExceptions.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "16:
\n17: \tfunction teardown(){
\n18: \t\tstructClear( request );
\n19: \t}
\n20:
\n" + }, { + "Raw_Trace": "system.basespec_cfc$cf.udfCall5(/testbox/system/BaseSpec.cfc:1011)", + "codePrintPlain": "1009: \t\t\t\t\t// execute teardown()\n1010: \t\t\t\t\tif( structKeyExists( this, \"teardown\" ) ){ this.teardown( currentMethod=arguments.spec.name ); }\n1011: \t\t\t\t}\n1012: \n1013: \t\t\t\t// store spec status\n", + "column": 0, + "line": 1011, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/BaseSpec.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "1009: \t\t\t\t\t// execute teardown()
\n1010: \t\t\t\t\tif( structKeyExists( this, "teardown" ) ){ this.teardown( currentMethod=arguments.spec.name ); }
\n1011: \t\t\t\t}
\n1012:
\n1013: \t\t\t\t// store spec status
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:208)", + "codePrintPlain": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,\n207: \t\t\t\t\t\trunner=this\n208: \t\t\t\t\t);\n209: \n210: \t\t\t\t\t// verify call backs\n", + "column": 0, + "line": 208, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,
\n207: \t\t\t\t\t\trunner=this
\n208: \t\t\t\t\t);
\n209:
\n210: \t\t\t\t\t// verify call backs
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:81)", + "codePrintPlain": "79: \t\t\t\t\t\ttestResults=arguments.testResults,\n80: \t\t\t\t\t\tbundleStats=bundleStats,\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks\n82: \t\t\t\t\t);\n83: \n", + "column": 0, + "line": 81, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "79: \t\t\t\t\t\ttestResults=arguments.testResults,
\n80: \t\t\t\t\t\tbundleStats=bundleStats,
\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks
\n82: \t\t\t\t\t);
\n83:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall2(/testbox/system/TestBox.cfc:478)", + "codePrintPlain": "476: \t\t\t\t// Run via xUnit Style\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );\n479: \t\t\t}\n480: \t\t} catch( Any e ){\n", + "column": 0, + "line": 478, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "476: \t\t\t\t// Run via xUnit Style
\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )
\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );
\n479: \t\t\t}
\n480: \t\t} catch( Any e ){
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:251)", + "codePrintPlain": "249: \t\t\t\tbundlePath = thisBundlePath,\n250: \t\t\t\ttestResults = results,\n251: \t\t\t\tcallbacks = arguments.callbacks\n252: \t\t\t);\n253: \n", + "column": 0, + "line": 251, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "249: \t\t\t\tbundlePath = thisBundlePath,
\n250: \t\t\t\ttestResults = results,
\n251: \t\t\t\tcallbacks = arguments.callbacks
\n252: \t\t\t);
\n253:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:160)", + "codePrintPlain": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }\n159: \t\t// run it and get results\n160: \t\tvar results = runRaw( argumentCollection=arguments );\n161: \t\t// store latest results\n162: \t\tvariables.result = results;\n", + "column": 0, + "line": 160, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }
\n159: \t\t// run it and get results
\n160: \t\tvar results = runRaw( argumentCollection=arguments );
\n161: \t\t// store latest results
\n162: \t\tvariables.result = results;
\n" + }, { + "Raw_Trace": "system.runners.htmlrunner_cfm$cf.call(/testbox/system/runners/HTMLRunner.cfm:49)", + "codePrintPlain": "47: \n48: // Run Tests using correct reporter\n49: results = testbox.run( reporter=url.reporter );\n50: \n51: // Write TEST.properties in report destination path.\n", + "column": 0, + "line": 49, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/HTMLRunner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "47:
\n48: // Run Tests using correct reporter
\n49: results = testbox.run( reporter=url.reporter );
\n50:
\n51: // Write TEST.properties in report destination path.
\n" + }, { + "Raw_Trace": "tests.runner_cfm$cf.call(/tests/runner.cfm:21)", + "codePrintPlain": "19: \n20: \n21: \n", + "column": 0, + "line": 21, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/runner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "19:
\n20: <!--- Include the TestBox HTML Runner --->
\n21: <cfinclude template="/testbox/system/runners/HTMLRunner.cfm" >
\n" + }], + "status": "Error", + "suiteID": "0F9E64ECB27A3F1E33BC78FB7266E610", + "endTime": 1553009673156, + "name": "testExpectedExceptionFromMethodWithTypeAndRegex", + "id": "5F2998DAA434E0999157C33D2998677F", + "failMessage": "" + }, { + "error": { + "Extended_Info": "", + "Message": "java.util.ConcurrentModificationException", + "Detail": "", + "additional": {}, + "TagContext": [{ + "Raw_Trace": "specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall1(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc:18)", + "codePrintPlain": "16: \n17: \tfunction teardown(){\n18: \t\tstructClear( request );\n19: \t}\n20: \n", + "column": 0, + "line": 18, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/specsWithFailures/MXUnitExpectedExceptions.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "16:
\n17: \tfunction teardown(){
\n18: \t\tstructClear( request );
\n19: \t}
\n20:
\n" + }, { + "Raw_Trace": "system.basespec_cfc$cf.udfCall5(/testbox/system/BaseSpec.cfc:1011)", + "codePrintPlain": "1009: \t\t\t\t\t// execute teardown()\n1010: \t\t\t\t\tif( structKeyExists( this, \"teardown\" ) ){ this.teardown( currentMethod=arguments.spec.name ); }\n1011: \t\t\t\t}\n1012: \n1013: \t\t\t\t// store spec status\n", + "column": 0, + "line": 1011, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/BaseSpec.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "1009: \t\t\t\t\t// execute teardown()
\n1010: \t\t\t\t\tif( structKeyExists( this, "teardown" ) ){ this.teardown( currentMethod=arguments.spec.name ); }
\n1011: \t\t\t\t}
\n1012:
\n1013: \t\t\t\t// store spec status
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:208)", + "codePrintPlain": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,\n207: \t\t\t\t\t\trunner=this\n208: \t\t\t\t\t);\n209: \n210: \t\t\t\t\t// verify call backs\n", + "column": 0, + "line": 208, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,
\n207: \t\t\t\t\t\trunner=this
\n208: \t\t\t\t\t);
\n209:
\n210: \t\t\t\t\t// verify call backs
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:81)", + "codePrintPlain": "79: \t\t\t\t\t\ttestResults=arguments.testResults,\n80: \t\t\t\t\t\tbundleStats=bundleStats,\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks\n82: \t\t\t\t\t);\n83: \n", + "column": 0, + "line": 81, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "79: \t\t\t\t\t\ttestResults=arguments.testResults,
\n80: \t\t\t\t\t\tbundleStats=bundleStats,
\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks
\n82: \t\t\t\t\t);
\n83:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall2(/testbox/system/TestBox.cfc:478)", + "codePrintPlain": "476: \t\t\t\t// Run via xUnit Style\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );\n479: \t\t\t}\n480: \t\t} catch( Any e ){\n", + "column": 0, + "line": 478, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "476: \t\t\t\t// Run via xUnit Style
\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )
\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );
\n479: \t\t\t}
\n480: \t\t} catch( Any e ){
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:251)", + "codePrintPlain": "249: \t\t\t\tbundlePath = thisBundlePath,\n250: \t\t\t\ttestResults = results,\n251: \t\t\t\tcallbacks = arguments.callbacks\n252: \t\t\t);\n253: \n", + "column": 0, + "line": 251, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "249: \t\t\t\tbundlePath = thisBundlePath,
\n250: \t\t\t\ttestResults = results,
\n251: \t\t\t\tcallbacks = arguments.callbacks
\n252: \t\t\t);
\n253:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:160)", + "codePrintPlain": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }\n159: \t\t// run it and get results\n160: \t\tvar results = runRaw( argumentCollection=arguments );\n161: \t\t// store latest results\n162: \t\tvariables.result = results;\n", + "column": 0, + "line": 160, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }
\n159: \t\t// run it and get results
\n160: \t\tvar results = runRaw( argumentCollection=arguments );
\n161: \t\t// store latest results
\n162: \t\tvariables.result = results;
\n" + }, { + "Raw_Trace": "system.runners.htmlrunner_cfm$cf.call(/testbox/system/runners/HTMLRunner.cfm:49)", + "codePrintPlain": "47: \n48: // Run Tests using correct reporter\n49: results = testbox.run( reporter=url.reporter );\n50: \n51: // Write TEST.properties in report destination path.\n", + "column": 0, + "line": 49, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/HTMLRunner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "47:
\n48: // Run Tests using correct reporter
\n49: results = testbox.run( reporter=url.reporter );
\n50:
\n51: // Write TEST.properties in report destination path.
\n" + }, { + "Raw_Trace": "tests.runner_cfm$cf.call(/tests/runner.cfm:21)", + "codePrintPlain": "19: \n20: \n21: \n", + "column": 0, + "line": 21, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/runner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "19:
\n20: <!--- Include the TestBox HTML Runner --->
\n21: <cfinclude template="/testbox/system/runners/HTMLRunner.cfm" >
\n" + }], + "ErrorCode": "0", + "type": "java.util.ConcurrentModificationException", + "StackTrace": "lucee.runtime.exp.NativeException: java.util.ConcurrentModificationException\n\tat java.util.HashMap$HashIterator.nextNode(HashMap.java:1442)\n\tat java.util.HashMap$KeyIterator.next(HashMap.java:1466)\n\tat io.undertow.servlet.util.IteratorEnumeration.nextElement(IteratorEnumeration.java:44)\n\tat lucee.runtime.type.scope.RequestImpl.clear(RequestImpl.java:157)\n\tat lucee.runtime.functions.struct.StructClear.call(StructClear.java:36)\n\tat specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall1(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc:18)\n\tat specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:695)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:2014)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.basespec_cfc$cf.udfCall5(/testbox/system/BaseSpec.cfc:1011)\n\tat system.basespec_cfc$cf.udfCall(/testbox/system/BaseSpec.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:695)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:2014)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:208)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:812)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:81)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.call(UDFImpl.java:226)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:693)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.call(ComponentImpl.java:1997)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithoutNamedValues(VariableUtilImpl.java:756)\n\tat lucee.runtime.PageContextImpl.getFunction(PageContextImpl.java:1718)\n\tat system.testbox_cfc$cf.udfCall2(/testbox/system/TestBox.cfc:478)\n\tat system.testbox_cfc$cf.udfCall(/testbox/system/TestBox.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:812)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:251)\n\tat system.testbox_cfc$cf.udfCall(/testbox/system/TestBox.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:812)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:160)\n\tat system.testbox_cfc$cf.udfCall(/testbox/system/TestBox.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:695)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:2014)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.runners.htmlrunner_cfm$cf.call(/testbox/system/runners/HTMLRunner.cfm:49)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:933)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:823)\n\tat lucee.runtime.PageContextImpl.doInclude(PageContextImpl.java:805)\n\tat tests.runner_cfm$cf.call(/tests/runner.cfm:21)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:933)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:823)\n\tat lucee.runtime.listener.ModernAppListener._onRequest(ModernAppListener.java:218)\n\tat lucee.runtime.listener.MixedAppListener.onRequest(MixedAppListener.java:43)\n\tat lucee.runtime.PageContextImpl.execute(PageContextImpl.java:2464)\n\tat lucee.runtime.PageContextImpl._execute(PageContextImpl.java:2454)\n\tat lucee.runtime.PageContextImpl.executeCFML(PageContextImpl.java:2427)\n\tat lucee.runtime.engine.Request.exe(Request.java:44)\n\tat lucee.runtime.engine.CFMLEngineImpl._service(CFMLEngineImpl.java:1090)\n\tat lucee.runtime.engine.CFMLEngineImpl.serviceCFML(CFMLEngineImpl.java:1038)\n\tat lucee.loader.engine.CFMLEngineWrapper.serviceCFML(CFMLEngineWrapper.java:102)\n\tat lucee.loader.servlet.CFMLServlet.service(CFMLServlet.java:51)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:790)\n\tat io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)\n\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)\n\tat org.cfmlprojects.regexpathinfofilter.RegexPathInfoFilter.doFilter(RegexPathInfoFilter.java:47)\n\tat io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)\n\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)\n\tat sun.reflect.GeneratedMethodAccessor53.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat com.intergral.fusionreactor.j2ee.filterchain.WrappedFilterChain.doFilter(WrappedFilterChain.java:134)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doNext(FusionReactorRequestHandler.java:764)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doHttpServletRequest(FusionReactorRequestHandler.java:344)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doFusionRequest(FusionReactorRequestHandler.java:207)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.handle(FusionReactorRequestHandler.java:801)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorCoreFilter.doFilter(FusionReactorCoreFilter.java:36)\n\tat sun.reflect.GeneratedMethodAccessor51.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat com.intergral.fusionreactor.j2ee.filterchain.WrappedFilterChain.doFilter(WrappedFilterChain.java:71)\n\tat sun.reflect.GeneratedMethodAccessor50.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat com.intergral.fusionreactor.agent.filter.FusionReactorStaticFilter.doFilter(FusionReactorStaticFilter.java:54)\n\tat com.intergral.fusionreactor.agent.pointcuts.NewFilterChainPointCut$1.invoke(NewFilterChainPointCut.java:41)\n\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java)\n\tat io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)\n\tat io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)\n\tat io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:64)\n\tat io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)\n\tat io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)\n\tat io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)\n\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n\tat io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)\n\tat io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)\n\tat io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)\n\tat io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)\n\tat io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)\n\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:81)\n\tat io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:138)\n\tat io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135)\n\tat io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)\n\tat io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81)\n\tat io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:104)\n\tat io.undertow.server.Connectors.executeRootHandler(Connectors.java:336)\n\tat io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\n\tat java.lang.Thread.run(Thread.java:748)\nCaused by: java.util.ConcurrentModificationException\n\t... 137 more\n", + "ExtendedInfo": "" + }, + "startTime": 1553009673156, + "totalDuration": 4, + "failOrigin": [{ + "Raw_Trace": "specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall1(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc:18)", + "codePrintPlain": "16: \n17: \tfunction teardown(){\n18: \t\tstructClear( request );\n19: \t}\n20: \n", + "column": 0, + "line": 18, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/specsWithFailures/MXUnitExpectedExceptions.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "16:
\n17: \tfunction teardown(){
\n18: \t\tstructClear( request );
\n19: \t}
\n20:
\n" + }, { + "Raw_Trace": "system.basespec_cfc$cf.udfCall5(/testbox/system/BaseSpec.cfc:1011)", + "codePrintPlain": "1009: \t\t\t\t\t// execute teardown()\n1010: \t\t\t\t\tif( structKeyExists( this, \"teardown\" ) ){ this.teardown( currentMethod=arguments.spec.name ); }\n1011: \t\t\t\t}\n1012: \n1013: \t\t\t\t// store spec status\n", + "column": 0, + "line": 1011, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/BaseSpec.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "1009: \t\t\t\t\t// execute teardown()
\n1010: \t\t\t\t\tif( structKeyExists( this, "teardown" ) ){ this.teardown( currentMethod=arguments.spec.name ); }
\n1011: \t\t\t\t}
\n1012:
\n1013: \t\t\t\t// store spec status
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:208)", + "codePrintPlain": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,\n207: \t\t\t\t\t\trunner=this\n208: \t\t\t\t\t);\n209: \n210: \t\t\t\t\t// verify call backs\n", + "column": 0, + "line": 208, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,
\n207: \t\t\t\t\t\trunner=this
\n208: \t\t\t\t\t);
\n209:
\n210: \t\t\t\t\t// verify call backs
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:81)", + "codePrintPlain": "79: \t\t\t\t\t\ttestResults=arguments.testResults,\n80: \t\t\t\t\t\tbundleStats=bundleStats,\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks\n82: \t\t\t\t\t);\n83: \n", + "column": 0, + "line": 81, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "79: \t\t\t\t\t\ttestResults=arguments.testResults,
\n80: \t\t\t\t\t\tbundleStats=bundleStats,
\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks
\n82: \t\t\t\t\t);
\n83:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall2(/testbox/system/TestBox.cfc:478)", + "codePrintPlain": "476: \t\t\t\t// Run via xUnit Style\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );\n479: \t\t\t}\n480: \t\t} catch( Any e ){\n", + "column": 0, + "line": 478, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "476: \t\t\t\t// Run via xUnit Style
\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )
\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );
\n479: \t\t\t}
\n480: \t\t} catch( Any e ){
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:251)", + "codePrintPlain": "249: \t\t\t\tbundlePath = thisBundlePath,\n250: \t\t\t\ttestResults = results,\n251: \t\t\t\tcallbacks = arguments.callbacks\n252: \t\t\t);\n253: \n", + "column": 0, + "line": 251, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "249: \t\t\t\tbundlePath = thisBundlePath,
\n250: \t\t\t\ttestResults = results,
\n251: \t\t\t\tcallbacks = arguments.callbacks
\n252: \t\t\t);
\n253:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:160)", + "codePrintPlain": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }\n159: \t\t// run it and get results\n160: \t\tvar results = runRaw( argumentCollection=arguments );\n161: \t\t// store latest results\n162: \t\tvariables.result = results;\n", + "column": 0, + "line": 160, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }
\n159: \t\t// run it and get results
\n160: \t\tvar results = runRaw( argumentCollection=arguments );
\n161: \t\t// store latest results
\n162: \t\tvariables.result = results;
\n" + }, { + "Raw_Trace": "system.runners.htmlrunner_cfm$cf.call(/testbox/system/runners/HTMLRunner.cfm:49)", + "codePrintPlain": "47: \n48: // Run Tests using correct reporter\n49: results = testbox.run( reporter=url.reporter );\n50: \n51: // Write TEST.properties in report destination path.\n", + "column": 0, + "line": 49, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/HTMLRunner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "47:
\n48: // Run Tests using correct reporter
\n49: results = testbox.run( reporter=url.reporter );
\n50:
\n51: // Write TEST.properties in report destination path.
\n" + }, { + "Raw_Trace": "tests.runner_cfm$cf.call(/tests/runner.cfm:21)", + "codePrintPlain": "19: \n20: \n21: \n", + "column": 0, + "line": 21, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/runner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "19:
\n20: <!--- Include the TestBox HTML Runner --->
\n21: <cfinclude template="/testbox/system/runners/HTMLRunner.cfm" >
\n" + }], + "status": "Error", + "suiteID": "0F9E64ECB27A3F1E33BC78FB7266E610", + "endTime": 1553009673160, + "name": "testExpectedExceptionWithValue", + "id": "821A8EBFF6CCAD78FF707B72D7566B3E", + "failMessage": "" + }, { + "error": { + "Extended_Info": "", + "Message": "java.util.ConcurrentModificationException", + "Detail": "", + "additional": {}, + "TagContext": [{ + "Raw_Trace": "specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall1(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc:18)", + "codePrintPlain": "16: \n17: \tfunction teardown(){\n18: \t\tstructClear( request );\n19: \t}\n20: \n", + "column": 0, + "line": 18, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/specsWithFailures/MXUnitExpectedExceptions.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "16:
\n17: \tfunction teardown(){
\n18: \t\tstructClear( request );
\n19: \t}
\n20:
\n" + }, { + "Raw_Trace": "system.basespec_cfc$cf.udfCall5(/testbox/system/BaseSpec.cfc:1010)", + "codePrintPlain": "1008: \t\t\t\t} finally {\n1009: \t\t\t\t\t// execute teardown()\n1010: \t\t\t\t\tif( structKeyExists( this, \"teardown\" ) ){ this.teardown( currentMethod=arguments.spec.name ); }\n1011: \t\t\t\t}\n1012: \n", + "column": 0, + "line": 1010, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/BaseSpec.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "1008: \t\t\t\t} finally {
\n1009: \t\t\t\t\t// execute teardown()
\n1010: \t\t\t\t\tif( structKeyExists( this, "teardown" ) ){ this.teardown( currentMethod=arguments.spec.name ); }
\n1011: \t\t\t\t}
\n1012:
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:208)", + "codePrintPlain": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,\n207: \t\t\t\t\t\trunner=this\n208: \t\t\t\t\t);\n209: \n210: \t\t\t\t\t// verify call backs\n", + "column": 0, + "line": 208, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,
\n207: \t\t\t\t\t\trunner=this
\n208: \t\t\t\t\t);
\n209:
\n210: \t\t\t\t\t// verify call backs
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:81)", + "codePrintPlain": "79: \t\t\t\t\t\ttestResults=arguments.testResults,\n80: \t\t\t\t\t\tbundleStats=bundleStats,\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks\n82: \t\t\t\t\t);\n83: \n", + "column": 0, + "line": 81, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "79: \t\t\t\t\t\ttestResults=arguments.testResults,
\n80: \t\t\t\t\t\tbundleStats=bundleStats,
\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks
\n82: \t\t\t\t\t);
\n83:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall2(/testbox/system/TestBox.cfc:478)", + "codePrintPlain": "476: \t\t\t\t// Run via xUnit Style\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );\n479: \t\t\t}\n480: \t\t} catch( Any e ){\n", + "column": 0, + "line": 478, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "476: \t\t\t\t// Run via xUnit Style
\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )
\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );
\n479: \t\t\t}
\n480: \t\t} catch( Any e ){
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:251)", + "codePrintPlain": "249: \t\t\t\tbundlePath = thisBundlePath,\n250: \t\t\t\ttestResults = results,\n251: \t\t\t\tcallbacks = arguments.callbacks\n252: \t\t\t);\n253: \n", + "column": 0, + "line": 251, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "249: \t\t\t\tbundlePath = thisBundlePath,
\n250: \t\t\t\ttestResults = results,
\n251: \t\t\t\tcallbacks = arguments.callbacks
\n252: \t\t\t);
\n253:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:160)", + "codePrintPlain": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }\n159: \t\t// run it and get results\n160: \t\tvar results = runRaw( argumentCollection=arguments );\n161: \t\t// store latest results\n162: \t\tvariables.result = results;\n", + "column": 0, + "line": 160, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }
\n159: \t\t// run it and get results
\n160: \t\tvar results = runRaw( argumentCollection=arguments );
\n161: \t\t// store latest results
\n162: \t\tvariables.result = results;
\n" + }, { + "Raw_Trace": "system.runners.htmlrunner_cfm$cf.call(/testbox/system/runners/HTMLRunner.cfm:49)", + "codePrintPlain": "47: \n48: // Run Tests using correct reporter\n49: results = testbox.run( reporter=url.reporter );\n50: \n51: // Write TEST.properties in report destination path.\n", + "column": 0, + "line": 49, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/HTMLRunner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "47:
\n48: // Run Tests using correct reporter
\n49: results = testbox.run( reporter=url.reporter );
\n50:
\n51: // Write TEST.properties in report destination path.
\n" + }, { + "Raw_Trace": "tests.runner_cfm$cf.call(/tests/runner.cfm:21)", + "codePrintPlain": "19: \n20: \n21: \n", + "column": 0, + "line": 21, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/runner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "19:
\n20: <!--- Include the TestBox HTML Runner --->
\n21: <cfinclude template="/testbox/system/runners/HTMLRunner.cfm" >
\n" + }], + "ErrorCode": "0", + "type": "java.util.ConcurrentModificationException", + "StackTrace": "lucee.runtime.exp.NativeException: java.util.ConcurrentModificationException\n\tat java.util.HashMap$HashIterator.nextNode(HashMap.java:1442)\n\tat java.util.HashMap$KeyIterator.next(HashMap.java:1466)\n\tat io.undertow.servlet.util.IteratorEnumeration.nextElement(IteratorEnumeration.java:44)\n\tat lucee.runtime.type.scope.RequestImpl.clear(RequestImpl.java:157)\n\tat lucee.runtime.functions.struct.StructClear.call(StructClear.java:36)\n\tat specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall1(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc:18)\n\tat specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:695)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:2014)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.basespec_cfc$cf.udfCall5(/testbox/system/BaseSpec.cfc:1010)\n\tat system.basespec_cfc$cf.udfCall(/testbox/system/BaseSpec.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:695)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:2014)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:208)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:812)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:81)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.call(UDFImpl.java:226)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:693)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.call(ComponentImpl.java:1997)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithoutNamedValues(VariableUtilImpl.java:756)\n\tat lucee.runtime.PageContextImpl.getFunction(PageContextImpl.java:1718)\n\tat system.testbox_cfc$cf.udfCall2(/testbox/system/TestBox.cfc:478)\n\tat system.testbox_cfc$cf.udfCall(/testbox/system/TestBox.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:812)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:251)\n\tat system.testbox_cfc$cf.udfCall(/testbox/system/TestBox.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:812)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:160)\n\tat system.testbox_cfc$cf.udfCall(/testbox/system/TestBox.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:695)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:2014)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.runners.htmlrunner_cfm$cf.call(/testbox/system/runners/HTMLRunner.cfm:49)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:933)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:823)\n\tat lucee.runtime.PageContextImpl.doInclude(PageContextImpl.java:805)\n\tat tests.runner_cfm$cf.call(/tests/runner.cfm:21)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:933)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:823)\n\tat lucee.runtime.listener.ModernAppListener._onRequest(ModernAppListener.java:218)\n\tat lucee.runtime.listener.MixedAppListener.onRequest(MixedAppListener.java:43)\n\tat lucee.runtime.PageContextImpl.execute(PageContextImpl.java:2464)\n\tat lucee.runtime.PageContextImpl._execute(PageContextImpl.java:2454)\n\tat lucee.runtime.PageContextImpl.executeCFML(PageContextImpl.java:2427)\n\tat lucee.runtime.engine.Request.exe(Request.java:44)\n\tat lucee.runtime.engine.CFMLEngineImpl._service(CFMLEngineImpl.java:1090)\n\tat lucee.runtime.engine.CFMLEngineImpl.serviceCFML(CFMLEngineImpl.java:1038)\n\tat lucee.loader.engine.CFMLEngineWrapper.serviceCFML(CFMLEngineWrapper.java:102)\n\tat lucee.loader.servlet.CFMLServlet.service(CFMLServlet.java:51)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:790)\n\tat io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)\n\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)\n\tat org.cfmlprojects.regexpathinfofilter.RegexPathInfoFilter.doFilter(RegexPathInfoFilter.java:47)\n\tat io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)\n\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)\n\tat sun.reflect.GeneratedMethodAccessor53.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat com.intergral.fusionreactor.j2ee.filterchain.WrappedFilterChain.doFilter(WrappedFilterChain.java:134)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doNext(FusionReactorRequestHandler.java:764)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doHttpServletRequest(FusionReactorRequestHandler.java:344)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doFusionRequest(FusionReactorRequestHandler.java:207)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.handle(FusionReactorRequestHandler.java:801)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorCoreFilter.doFilter(FusionReactorCoreFilter.java:36)\n\tat sun.reflect.GeneratedMethodAccessor51.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat com.intergral.fusionreactor.j2ee.filterchain.WrappedFilterChain.doFilter(WrappedFilterChain.java:71)\n\tat sun.reflect.GeneratedMethodAccessor50.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat com.intergral.fusionreactor.agent.filter.FusionReactorStaticFilter.doFilter(FusionReactorStaticFilter.java:54)\n\tat com.intergral.fusionreactor.agent.pointcuts.NewFilterChainPointCut$1.invoke(NewFilterChainPointCut.java:41)\n\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java)\n\tat io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)\n\tat io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)\n\tat io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:64)\n\tat io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)\n\tat io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)\n\tat io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)\n\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n\tat io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)\n\tat io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)\n\tat io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)\n\tat io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)\n\tat io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)\n\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:81)\n\tat io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:138)\n\tat io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135)\n\tat io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)\n\tat io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81)\n\tat io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:104)\n\tat io.undertow.server.Connectors.executeRootHandler(Connectors.java:336)\n\tat io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\n\tat java.lang.Thread.run(Thread.java:748)\nCaused by: java.util.ConcurrentModificationException\n\t... 137 more\n", + "ExtendedInfo": "" + }, + "startTime": 1553009673160, + "totalDuration": 3, + "failOrigin": [{ + "Raw_Trace": "specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall1(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc:18)", + "codePrintPlain": "16: \n17: \tfunction teardown(){\n18: \t\tstructClear( request );\n19: \t}\n20: \n", + "column": 0, + "line": 18, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/specsWithFailures/MXUnitExpectedExceptions.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "16:
\n17: \tfunction teardown(){
\n18: \t\tstructClear( request );
\n19: \t}
\n20:
\n" + }, { + "Raw_Trace": "system.basespec_cfc$cf.udfCall5(/testbox/system/BaseSpec.cfc:1010)", + "codePrintPlain": "1008: \t\t\t\t} finally {\n1009: \t\t\t\t\t// execute teardown()\n1010: \t\t\t\t\tif( structKeyExists( this, \"teardown\" ) ){ this.teardown( currentMethod=arguments.spec.name ); }\n1011: \t\t\t\t}\n1012: \n", + "column": 0, + "line": 1010, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/BaseSpec.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "1008: \t\t\t\t} finally {
\n1009: \t\t\t\t\t// execute teardown()
\n1010: \t\t\t\t\tif( structKeyExists( this, "teardown" ) ){ this.teardown( currentMethod=arguments.spec.name ); }
\n1011: \t\t\t\t}
\n1012:
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:208)", + "codePrintPlain": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,\n207: \t\t\t\t\t\trunner=this\n208: \t\t\t\t\t);\n209: \n210: \t\t\t\t\t// verify call backs\n", + "column": 0, + "line": 208, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,
\n207: \t\t\t\t\t\trunner=this
\n208: \t\t\t\t\t);
\n209:
\n210: \t\t\t\t\t// verify call backs
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:81)", + "codePrintPlain": "79: \t\t\t\t\t\ttestResults=arguments.testResults,\n80: \t\t\t\t\t\tbundleStats=bundleStats,\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks\n82: \t\t\t\t\t);\n83: \n", + "column": 0, + "line": 81, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "79: \t\t\t\t\t\ttestResults=arguments.testResults,
\n80: \t\t\t\t\t\tbundleStats=bundleStats,
\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks
\n82: \t\t\t\t\t);
\n83:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall2(/testbox/system/TestBox.cfc:478)", + "codePrintPlain": "476: \t\t\t\t// Run via xUnit Style\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );\n479: \t\t\t}\n480: \t\t} catch( Any e ){\n", + "column": 0, + "line": 478, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "476: \t\t\t\t// Run via xUnit Style
\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )
\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );
\n479: \t\t\t}
\n480: \t\t} catch( Any e ){
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:251)", + "codePrintPlain": "249: \t\t\t\tbundlePath = thisBundlePath,\n250: \t\t\t\ttestResults = results,\n251: \t\t\t\tcallbacks = arguments.callbacks\n252: \t\t\t);\n253: \n", + "column": 0, + "line": 251, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "249: \t\t\t\tbundlePath = thisBundlePath,
\n250: \t\t\t\ttestResults = results,
\n251: \t\t\t\tcallbacks = arguments.callbacks
\n252: \t\t\t);
\n253:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:160)", + "codePrintPlain": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }\n159: \t\t// run it and get results\n160: \t\tvar results = runRaw( argumentCollection=arguments );\n161: \t\t// store latest results\n162: \t\tvariables.result = results;\n", + "column": 0, + "line": 160, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }
\n159: \t\t// run it and get results
\n160: \t\tvar results = runRaw( argumentCollection=arguments );
\n161: \t\t// store latest results
\n162: \t\tvariables.result = results;
\n" + }, { + "Raw_Trace": "system.runners.htmlrunner_cfm$cf.call(/testbox/system/runners/HTMLRunner.cfm:49)", + "codePrintPlain": "47: \n48: // Run Tests using correct reporter\n49: results = testbox.run( reporter=url.reporter );\n50: \n51: // Write TEST.properties in report destination path.\n", + "column": 0, + "line": 49, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/HTMLRunner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "47:
\n48: // Run Tests using correct reporter
\n49: results = testbox.run( reporter=url.reporter );
\n50:
\n51: // Write TEST.properties in report destination path.
\n" + }, { + "Raw_Trace": "tests.runner_cfm$cf.call(/tests/runner.cfm:21)", + "codePrintPlain": "19: \n20: \n21: \n", + "column": 0, + "line": 21, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/runner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "19:
\n20: <!--- Include the TestBox HTML Runner --->
\n21: <cfinclude template="/testbox/system/runners/HTMLRunner.cfm" >
\n" + }], + "status": "Error", + "suiteID": "0F9E64ECB27A3F1E33BC78FB7266E610", + "endTime": 1553009673163, + "name": "testRaiseException_fail_wrong_exception_raised", + "id": "38E9FBD5CD24CC185DB762502F1D2E1E", + "failMessage": "" + }, { + "error": { + "Extended_Info": "", + "Message": "java.util.ConcurrentModificationException", + "Detail": "", + "additional": {}, + "TagContext": [{ + "Raw_Trace": "specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall1(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc:18)", + "codePrintPlain": "16: \n17: \tfunction teardown(){\n18: \t\tstructClear( request );\n19: \t}\n20: \n", + "column": 0, + "line": 18, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/specsWithFailures/MXUnitExpectedExceptions.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "16:
\n17: \tfunction teardown(){
\n18: \t\tstructClear( request );
\n19: \t}
\n20:
\n" + }, { + "Raw_Trace": "system.basespec_cfc$cf.udfCall5(/testbox/system/BaseSpec.cfc:1010)", + "codePrintPlain": "1008: \t\t\t\t} finally {\n1009: \t\t\t\t\t// execute teardown()\n1010: \t\t\t\t\tif( structKeyExists( this, \"teardown\" ) ){ this.teardown( currentMethod=arguments.spec.name ); }\n1011: \t\t\t\t}\n1012: \n", + "column": 0, + "line": 1010, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/BaseSpec.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "1008: \t\t\t\t} finally {
\n1009: \t\t\t\t\t// execute teardown()
\n1010: \t\t\t\t\tif( structKeyExists( this, "teardown" ) ){ this.teardown( currentMethod=arguments.spec.name ); }
\n1011: \t\t\t\t}
\n1012:
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:208)", + "codePrintPlain": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,\n207: \t\t\t\t\t\trunner=this\n208: \t\t\t\t\t);\n209: \n210: \t\t\t\t\t// verify call backs\n", + "column": 0, + "line": 208, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,
\n207: \t\t\t\t\t\trunner=this
\n208: \t\t\t\t\t);
\n209:
\n210: \t\t\t\t\t// verify call backs
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:81)", + "codePrintPlain": "79: \t\t\t\t\t\ttestResults=arguments.testResults,\n80: \t\t\t\t\t\tbundleStats=bundleStats,\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks\n82: \t\t\t\t\t);\n83: \n", + "column": 0, + "line": 81, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "79: \t\t\t\t\t\ttestResults=arguments.testResults,
\n80: \t\t\t\t\t\tbundleStats=bundleStats,
\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks
\n82: \t\t\t\t\t);
\n83:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall2(/testbox/system/TestBox.cfc:478)", + "codePrintPlain": "476: \t\t\t\t// Run via xUnit Style\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );\n479: \t\t\t}\n480: \t\t} catch( Any e ){\n", + "column": 0, + "line": 478, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "476: \t\t\t\t// Run via xUnit Style
\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )
\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );
\n479: \t\t\t}
\n480: \t\t} catch( Any e ){
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:251)", + "codePrintPlain": "249: \t\t\t\tbundlePath = thisBundlePath,\n250: \t\t\t\ttestResults = results,\n251: \t\t\t\tcallbacks = arguments.callbacks\n252: \t\t\t);\n253: \n", + "column": 0, + "line": 251, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "249: \t\t\t\tbundlePath = thisBundlePath,
\n250: \t\t\t\ttestResults = results,
\n251: \t\t\t\tcallbacks = arguments.callbacks
\n252: \t\t\t);
\n253:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:160)", + "codePrintPlain": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }\n159: \t\t// run it and get results\n160: \t\tvar results = runRaw( argumentCollection=arguments );\n161: \t\t// store latest results\n162: \t\tvariables.result = results;\n", + "column": 0, + "line": 160, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }
\n159: \t\t// run it and get results
\n160: \t\tvar results = runRaw( argumentCollection=arguments );
\n161: \t\t// store latest results
\n162: \t\tvariables.result = results;
\n" + }, { + "Raw_Trace": "system.runners.htmlrunner_cfm$cf.call(/testbox/system/runners/HTMLRunner.cfm:49)", + "codePrintPlain": "47: \n48: // Run Tests using correct reporter\n49: results = testbox.run( reporter=url.reporter );\n50: \n51: // Write TEST.properties in report destination path.\n", + "column": 0, + "line": 49, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/HTMLRunner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "47:
\n48: // Run Tests using correct reporter
\n49: results = testbox.run( reporter=url.reporter );
\n50:
\n51: // Write TEST.properties in report destination path.
\n" + }, { + "Raw_Trace": "tests.runner_cfm$cf.call(/tests/runner.cfm:21)", + "codePrintPlain": "19: \n20: \n21: \n", + "column": 0, + "line": 21, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/runner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "19:
\n20: <!--- Include the TestBox HTML Runner --->
\n21: <cfinclude template="/testbox/system/runners/HTMLRunner.cfm" >
\n" + }], + "ErrorCode": "0", + "type": "java.util.ConcurrentModificationException", + "StackTrace": "lucee.runtime.exp.NativeException: java.util.ConcurrentModificationException\n\tat java.util.HashMap$HashIterator.nextNode(HashMap.java:1442)\n\tat java.util.HashMap$KeyIterator.next(HashMap.java:1466)\n\tat io.undertow.servlet.util.IteratorEnumeration.nextElement(IteratorEnumeration.java:44)\n\tat lucee.runtime.type.scope.RequestImpl.clear(RequestImpl.java:157)\n\tat lucee.runtime.functions.struct.StructClear.call(StructClear.java:36)\n\tat specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall1(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc:18)\n\tat specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:695)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:2014)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.basespec_cfc$cf.udfCall5(/testbox/system/BaseSpec.cfc:1010)\n\tat system.basespec_cfc$cf.udfCall(/testbox/system/BaseSpec.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:695)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:2014)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:208)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:812)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:81)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.call(UDFImpl.java:226)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:693)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.call(ComponentImpl.java:1997)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithoutNamedValues(VariableUtilImpl.java:756)\n\tat lucee.runtime.PageContextImpl.getFunction(PageContextImpl.java:1718)\n\tat system.testbox_cfc$cf.udfCall2(/testbox/system/TestBox.cfc:478)\n\tat system.testbox_cfc$cf.udfCall(/testbox/system/TestBox.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:812)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:251)\n\tat system.testbox_cfc$cf.udfCall(/testbox/system/TestBox.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.type.scope.UndefinedImpl.callWithNamedValues(UndefinedImpl.java:812)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:160)\n\tat system.testbox_cfc$cf.udfCall(/testbox/system/TestBox.cfc)\n\tat lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:107)\n\tat lucee.runtime.type.UDFImpl._call(UDFImpl.java:357)\n\tat lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:212)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:695)\n\tat lucee.runtime.ComponentImpl._call(ComponentImpl.java:573)\n\tat lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:2014)\n\tat lucee.runtime.util.VariableUtilImpl.callFunctionWithNamedValues(VariableUtilImpl.java:833)\n\tat lucee.runtime.PageContextImpl.getFunctionWithNamedValues(PageContextImpl.java:1737)\n\tat system.runners.htmlrunner_cfm$cf.call(/testbox/system/runners/HTMLRunner.cfm:49)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:933)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:823)\n\tat lucee.runtime.PageContextImpl.doInclude(PageContextImpl.java:805)\n\tat tests.runner_cfm$cf.call(/tests/runner.cfm:21)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:933)\n\tat lucee.runtime.PageContextImpl._doInclude(PageContextImpl.java:823)\n\tat lucee.runtime.listener.ModernAppListener._onRequest(ModernAppListener.java:218)\n\tat lucee.runtime.listener.MixedAppListener.onRequest(MixedAppListener.java:43)\n\tat lucee.runtime.PageContextImpl.execute(PageContextImpl.java:2464)\n\tat lucee.runtime.PageContextImpl._execute(PageContextImpl.java:2454)\n\tat lucee.runtime.PageContextImpl.executeCFML(PageContextImpl.java:2427)\n\tat lucee.runtime.engine.Request.exe(Request.java:44)\n\tat lucee.runtime.engine.CFMLEngineImpl._service(CFMLEngineImpl.java:1090)\n\tat lucee.runtime.engine.CFMLEngineImpl.serviceCFML(CFMLEngineImpl.java:1038)\n\tat lucee.loader.engine.CFMLEngineWrapper.serviceCFML(CFMLEngineWrapper.java:102)\n\tat lucee.loader.servlet.CFMLServlet.service(CFMLServlet.java:51)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:790)\n\tat io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)\n\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)\n\tat org.cfmlprojects.regexpathinfofilter.RegexPathInfoFilter.doFilter(RegexPathInfoFilter.java:47)\n\tat io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)\n\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)\n\tat sun.reflect.GeneratedMethodAccessor53.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat com.intergral.fusionreactor.j2ee.filterchain.WrappedFilterChain.doFilter(WrappedFilterChain.java:134)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doNext(FusionReactorRequestHandler.java:764)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doHttpServletRequest(FusionReactorRequestHandler.java:344)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.doFusionRequest(FusionReactorRequestHandler.java:207)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorRequestHandler.handle(FusionReactorRequestHandler.java:801)\n\tat com.intergral.fusionreactor.j2ee.filter.FusionReactorCoreFilter.doFilter(FusionReactorCoreFilter.java:36)\n\tat sun.reflect.GeneratedMethodAccessor51.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat com.intergral.fusionreactor.j2ee.filterchain.WrappedFilterChain.doFilter(WrappedFilterChain.java:71)\n\tat sun.reflect.GeneratedMethodAccessor50.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat com.intergral.fusionreactor.agent.filter.FusionReactorStaticFilter.doFilter(FusionReactorStaticFilter.java:54)\n\tat com.intergral.fusionreactor.agent.pointcuts.NewFilterChainPointCut$1.invoke(NewFilterChainPointCut.java:41)\n\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java)\n\tat io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)\n\tat io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)\n\tat io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:64)\n\tat io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)\n\tat io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)\n\tat io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)\n\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n\tat io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)\n\tat io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)\n\tat io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)\n\tat io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)\n\tat io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)\n\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:81)\n\tat io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:138)\n\tat io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135)\n\tat io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)\n\tat io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272)\n\tat io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81)\n\tat io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:104)\n\tat io.undertow.server.Connectors.executeRootHandler(Connectors.java:336)\n\tat io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\n\tat java.lang.Thread.run(Thread.java:748)\nCaused by: java.util.ConcurrentModificationException\n\t... 137 more\n", + "ExtendedInfo": "" + }, + "startTime": 1553009673163, + "totalDuration": 4, + "failOrigin": [{ + "Raw_Trace": "specswithfailures.mxunitexpectedexceptions_cfc$cf.udfCall1(/tests/specsWithFailures/MXUnitExpectedExceptions.cfc:18)", + "codePrintPlain": "16: \n17: \tfunction teardown(){\n18: \t\tstructClear( request );\n19: \t}\n20: \n", + "column": 0, + "line": 18, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/specsWithFailures/MXUnitExpectedExceptions.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "16:
\n17: \tfunction teardown(){
\n18: \t\tstructClear( request );
\n19: \t}
\n20:
\n" + }, { + "Raw_Trace": "system.basespec_cfc$cf.udfCall5(/testbox/system/BaseSpec.cfc:1010)", + "codePrintPlain": "1008: \t\t\t\t} finally {\n1009: \t\t\t\t\t// execute teardown()\n1010: \t\t\t\t\tif( structKeyExists( this, \"teardown\" ) ){ this.teardown( currentMethod=arguments.spec.name ); }\n1011: \t\t\t\t}\n1012: \n", + "column": 0, + "line": 1010, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/BaseSpec.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "1008: \t\t\t\t} finally {
\n1009: \t\t\t\t\t// execute teardown()
\n1010: \t\t\t\t\tif( structKeyExists( this, "teardown" ) ){ this.teardown( currentMethod=arguments.spec.name ); }
\n1011: \t\t\t\t}
\n1012:
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:208)", + "codePrintPlain": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,\n207: \t\t\t\t\t\trunner=this\n208: \t\t\t\t\t);\n209: \n210: \t\t\t\t\t// verify call backs\n", + "column": 0, + "line": 208, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "206: \t\t\t\t\t\tsuiteStats=thread.suiteStats,
\n207: \t\t\t\t\t\trunner=this
\n208: \t\t\t\t\t);
\n209:
\n210: \t\t\t\t\t// verify call backs
\n" + }, { + "Raw_Trace": "system.runners.unitrunner_cfc$cf.udfCall(/testbox/system/runners/UnitRunner.cfc:81)", + "codePrintPlain": "79: \t\t\t\t\t\ttestResults=arguments.testResults,\n80: \t\t\t\t\t\tbundleStats=bundleStats,\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks\n82: \t\t\t\t\t);\n83: \n", + "column": 0, + "line": 81, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/UnitRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "79: \t\t\t\t\t\ttestResults=arguments.testResults,
\n80: \t\t\t\t\t\tbundleStats=bundleStats,
\n81: \t\t\t\t\t\tcallbacks=arguments.callbacks
\n82: \t\t\t\t\t);
\n83:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall2(/testbox/system/TestBox.cfc:478)", + "codePrintPlain": "476: \t\t\t\t// Run via xUnit Style\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );\n479: \t\t\t}\n480: \t\t} catch( Any e ){\n", + "column": 0, + "line": 478, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "476: \t\t\t\t// Run via xUnit Style
\n477: \t\t\t\tnew testbox.system.runners.UnitRunner( options=variables.options,testbox=this )
\n478: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );
\n479: \t\t\t}
\n480: \t\t} catch( Any e ){
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:251)", + "codePrintPlain": "249: \t\t\t\tbundlePath = thisBundlePath,\n250: \t\t\t\ttestResults = results,\n251: \t\t\t\tcallbacks = arguments.callbacks\n252: \t\t\t);\n253: \n", + "column": 0, + "line": 251, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "249: \t\t\t\tbundlePath = thisBundlePath,
\n250: \t\t\t\ttestResults = results,
\n251: \t\t\t\tcallbacks = arguments.callbacks
\n252: \t\t\t);
\n253:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:160)", + "codePrintPlain": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }\n159: \t\t// run it and get results\n160: \t\tvar results = runRaw( argumentCollection=arguments );\n161: \t\t// store latest results\n162: \t\tvariables.result = results;\n", + "column": 0, + "line": 160, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }
\n159: \t\t// run it and get results
\n160: \t\tvar results = runRaw( argumentCollection=arguments );
\n161: \t\t// store latest results
\n162: \t\tvariables.result = results;
\n" + }, { + "Raw_Trace": "system.runners.htmlrunner_cfm$cf.call(/testbox/system/runners/HTMLRunner.cfm:49)", + "codePrintPlain": "47: \n48: // Run Tests using correct reporter\n49: results = testbox.run( reporter=url.reporter );\n50: \n51: // Write TEST.properties in report destination path.\n", + "column": 0, + "line": 49, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/HTMLRunner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "47:
\n48: // Run Tests using correct reporter
\n49: results = testbox.run( reporter=url.reporter );
\n50:
\n51: // Write TEST.properties in report destination path.
\n" + }, { + "Raw_Trace": "tests.runner_cfm$cf.call(/tests/runner.cfm:21)", + "codePrintPlain": "19: \n20: \n21: \n", + "column": 0, + "line": 21, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/runner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "19:
\n20: <!--- Include the TestBox HTML Runner --->
\n21: <cfinclude template="/testbox/system/runners/HTMLRunner.cfm" >
\n" + }], + "status": "Error", + "suiteID": "0F9E64ECB27A3F1E33BC78FB7266E610", + "endTime": 1553009673167, + "name": "testExpectException_should_fail", + "id": "9AE953744ABF596DB46DABAAF47A36E0", + "failMessage": "" + }], + "endTime": 1553009673167, + "totalError": 7, + "name": "tests.specsWithFailures.MXUnitExpectedExceptions", + "id": "0F9E64ECB27A3F1E33BC78FB7266E610", + "suiteStats": [] + }], + "globalException": "" + }, { + "totalSuites": 1, + "startTime": 1553009673173, + "totalPass": 1, + "totalDuration": 15, + "totalSkipped": 0, + "totalFail": 1, + "totalSpecs": 2, + "path": "tests.specsWithFailures.TeardownWithFailureBDD", + "endTime": 1553009673188, + "totalError": 0, + "name": "tests.specsWithFailures.TeardownWithFailureBDD", + "id": "7DA7FE371A5DA234729404FCED6D1494", + "suiteStats": [{ + "startTime": 1553009673174, + "totalPass": 1, + "totalDuration": 13, + "totalSkipped": 0, + "totalFail": 1, + "totalSpecs": 2, + "bundleID": "7DA7FE371A5DA234729404FCED6D1494", + "status": "Failed", + "parentID": "", + "specStats": [{ + "error": {}, + "startTime": 1553009673174, + "totalDuration": 5, + "failOrigin": {}, + "status": "Passed", + "suiteID": "734EAC2A2E0173AB943C7F55FC61E4B4", + "endTime": 1553009673179, + "name": "passes", + "id": "60E1FD85154BCE145E454EB6A78FED66", + "failMessage": "" + }, { + "error": {}, + "startTime": 1553009673179, + "totalDuration": 8, + "failOrigin": [{ + "Raw_Trace": "specswithfailures.teardownwithfailurebdd_cfc$cf.udfCall(/tests/specsWithFailures/TeardownWithFailureBDD.cfc:34)", + "codePrintPlain": "32: \n33: \t\t\tit(\"fails\", function(){\n34: \t\t\t\texpect( 1 ).toBe( 3 );\n35: \t\t\t});\n36: \n", + "column": 0, + "line": 34, + "template": "/Users/iurquiza/Projects/ortus/TestBox/tests/specsWithFailures/TeardownWithFailureBDD.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "32:
\n33: \t\t\tit("fails", function(){
\n34: \t\t\t\texpect( 1 ).toBe( 3 );
\n35: \t\t\t});
\n36:
\n" + }, { + "Raw_Trace": "system.basespec_cfc$cf.udfCall6(/testbox/system/BaseSpec.cfc:1237)", + "codePrintPlain": "1235: \t\tstring implements=\"\"\n1236: \t){\n1237: \t\treturn this.$mockBox.createStub( argumentCollection=arguments );\n1238: \t}\n1239: \n", + "column": 0, + "line": 1237, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/BaseSpec.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "1235: \t\tstring implements=""
\n1236: \t){
\n1237: \t\treturn this.$mockBox.createStub( argumentCollection=arguments );
\n1238: \t}
\n1239:
\n" + }, { + "Raw_Trace": "system.basespec_cfc$cf.udfCall4(/testbox/system/BaseSpec.cfc:888)", + "codePrintPlain": "886: \t\t\treturn function(){\n887: \t\t\t\t// Execute the body of the spec\n888: \t\t\t\tnextClosure.body( spec = thread.spec, suite = thread.suite, data = nextClosure.data );\n889: \t\t\t};\n890: \t\t}\n", + "column": 0, + "line": 888, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/BaseSpec.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "886: \t\t\treturn function(){
\n887: \t\t\t\t// Execute the body of the spec
\n888: \t\t\t\tnextClosure.body( spec = thread.spec, suite = thread.suite, data = nextClosure.data );
\n889: \t\t\t};
\n890: \t\t}
\n" + }, { + "Raw_Trace": "system.basespec_cfc$cf.udfCall4(/testbox/system/BaseSpec.cfc:863)", + "codePrintPlain": "861: \t\t);\n862: \t\t// Run the specs\n863: \t\tspecStack();\n864: \n865: \t\treturn this;\n", + "column": 0, + "line": 863, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/BaseSpec.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "861: \t\t);
\n862: \t\t// Run the specs
\n863: \t\tspecStack();
\n864:
\n865: \t\treturn this;
\n" + }, { + "Raw_Trace": "system.basespec_cfc$cf.udfCall3(/testbox/system/BaseSpec.cfc:701)", + "codePrintPlain": "699: \n700: \t\t\t\ttry{\n701: \t\t\t\t\trunAroundEachClosures( arguments.suite, arguments.spec );\n702: \t\t\t\t} catch( any e ){\n703: \t\t\t\t\trethrow;\n", + "column": 0, + "line": 701, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/BaseSpec.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "699:
\n700: \t\t\t\ttry{
\n701: \t\t\t\t\trunAroundEachClosures( arguments.suite, arguments.spec );
\n702: \t\t\t\t} catch( any e ){
\n703: \t\t\t\t\trethrow;
\n" + }, { + "Raw_Trace": "system.runners.bddrunner_cfc$cf.udfCall(/testbox/system/runners/BDDRunner.cfc:221)", + "codePrintPlain": "219: \t\t\t\t\t\tsuiteStats=thread.suiteStats,\n220: \t\t\t\t\t\trunner=this\n221: \t\t\t\t\t);\n222: \n223: \t\t\t\t\t// verify call backs\n", + "column": 0, + "line": 221, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/BDDRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "219: \t\t\t\t\t\tsuiteStats=thread.suiteStats,
\n220: \t\t\t\t\t\trunner=this
\n221: \t\t\t\t\t);
\n222:
\n223: \t\t\t\t\t// verify call backs
\n" + }, { + "Raw_Trace": "system.runners.bddrunner_cfc$cf.udfCall(/testbox/system/runners/BDDRunner.cfc:83)", + "codePrintPlain": "81: \t\t\t\t\t\ttestResults=arguments.testResults,\n82: \t\t\t\t\t\tbundleStats=bundleStats,\n83: \t\t\t\t\t\tcallbacks=arguments.callbacks\n84: \t\t\t\t\t);\n85: \n", + "column": 0, + "line": 83, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/BDDRunner.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "81: \t\t\t\t\t\ttestResults=arguments.testResults,
\n82: \t\t\t\t\t\tbundleStats=bundleStats,
\n83: \t\t\t\t\t\tcallbacks=arguments.callbacks
\n84: \t\t\t\t\t);
\n85:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall2(/testbox/system/TestBox.cfc:473)", + "codePrintPlain": "471: \t\t\t\t// Run via BDD Style\n472: \t\t\t\tnew testbox.system.runners.BDDRunner( options=variables.options, testbox=this )\n473: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );\n474: \t\t\t}\n475: \t\t\telse{\n", + "column": 0, + "line": 473, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "471: \t\t\t\t// Run via BDD Style
\n472: \t\t\t\tnew testbox.system.runners.BDDRunner( options=variables.options, testbox=this )
\n473: \t\t\t\t\t.run( target, arguments.testResults, arguments.callbacks );
\n474: \t\t\t}
\n475: \t\t\telse{
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:251)", + "codePrintPlain": "249: \t\t\t\tbundlePath = thisBundlePath,\n250: \t\t\t\ttestResults = results,\n251: \t\t\t\tcallbacks = arguments.callbacks\n252: \t\t\t);\n253: \n", + "column": 0, + "line": 251, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "249: \t\t\t\tbundlePath = thisBundlePath,
\n250: \t\t\t\ttestResults = results,
\n251: \t\t\t\tcallbacks = arguments.callbacks
\n252: \t\t\t);
\n253:
\n" + }, { + "Raw_Trace": "system.testbox_cfc$cf.udfCall1(/testbox/system/TestBox.cfc:160)", + "codePrintPlain": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }\n159: \t\t// run it and get results\n160: \t\tvar results = runRaw( argumentCollection=arguments );\n161: \t\t// store latest results\n162: \t\tvariables.result = results;\n", + "column": 0, + "line": 160, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/TestBox.cfc", + "id": "??", + "type": "cfml", + "codePrintHTML": "158: \t\tif( !isNull( arguments.reporter ) ){ variables.reporter = arguments.reporter; }
\n159: \t\t// run it and get results
\n160: \t\tvar results = runRaw( argumentCollection=arguments );
\n161: \t\t// store latest results
\n162: \t\tvariables.result = results;
\n" + }, { + "Raw_Trace": "system.runners.htmlrunner_cfm$cf.call(/testbox/system/runners/HTMLRunner.cfm:49)", + "codePrintPlain": "47: \n48: // Run Tests using correct reporter\n49: results = testbox.run( reporter=url.reporter );\n50: \n51: // Write TEST.properties in report destination path.\n", + "column": 0, + "line": 49, + "template": "/Users/iurquiza/Projects/ortus/TestBox/system/runners/HTMLRunner.cfm", + "id": "??", + "type": "cfml", + "codePrintHTML": "47:
\n48: // Run Tests using correct reporter
\n49: results = testbox.run( reporter=url.reporter );
\n50:
\n51: // Write TEST.properties in report destination path.
\n" + }], + "status": "Failed", + "suiteID": "734EAC2A2E0173AB943C7F55FC61E4B4", + "endTime": 1553009673187, + "name": "fails", + "id": "5C518249D53779A16FA8302C61C4DDDA", + "failMessage": "Expected [3] but received [1]" + }], + "endTime": 1553009673187, + "totalError": 0, + "name": "A suite", + "id": "734EAC2A2E0173AB943C7F55FC61E4B4", + "suiteStats": [] + }], + "globalException": "" + }], + "totalPass": 2, + "totalDuration": 115, + "version": "@build.version@+@build.number@", + "totalSkipped": 0, + "totalFail": 2, + "totalSpecs": 11, + "excludes": ["exclude1", "exclude2"], + "labels": ["label1", "label2"], + "resultID": "", + "endTime": 1553009673188, + "coverage": { + "data": { + "sonarQubeResults": "/Users/iurquiza/Projects/ortus/TestBox/tests/results/sonarQubeResults", + "browserResults": "/Users/iurquiza/Projects/ortus/TestBox/tests/results/coverageReport", + "stats": { + "totalCoveredLines": 1253, + "numFiles": 78, + "percTotalCoverage": 0.167022127433, + "totalExecutableLines": 7502 + } + }, + "enabled": true + }, + "totalError": 7, + "totalBundles": 3 +} \ No newline at end of file diff --git a/src/cfml/system/modules_app/testbox-commands/templates/testbox/bdd.txt b/src/cfml/system/modules_app/testbox-commands/templates/testbox/test-visualizer/bdd.txt similarity index 100% rename from src/cfml/system/modules_app/testbox-commands/templates/testbox/bdd.txt rename to src/cfml/system/modules_app/testbox-commands/templates/testbox/test-visualizer/bdd.txt diff --git a/src/cfml/system/modules_app/testbox-commands/templates/testbox/test-visualizer/index.html b/src/cfml/system/modules_app/testbox-commands/templates/testbox/test-visualizer/index.html new file mode 100644 index 000000000..292a82435 --- /dev/null +++ b/src/cfml/system/modules_app/testbox-commands/templates/testbox/test-visualizer/index.html @@ -0,0 +1,556 @@ + + + + + + Test Visualizer + + + + + + + + + + +
+
+
+
+ + v@build.version@+@build.number@ +
+
+
+ +
+ + +
+
+
+
+
+
+ Loading... +
+
+
+
+
+
+
+
+
+ + diff --git a/src/cfml/system/modules_app/testbox-commands/templates/testbox/test-visualizer/main.css b/src/cfml/system/modules_app/testbox-commands/templates/testbox/test-visualizer/main.css new file mode 100644 index 000000000..310d54737 --- /dev/null +++ b/src/cfml/system/modules_app/testbox-commands/templates/testbox/test-visualizer/main.css @@ -0,0 +1,7615 @@ +/* Overriden Styles */ +/*! + * Bootstrap v4.3.1 (https://getbootstrap.com/) + * Copyright 2011-2019 The Bootstrap Authors + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +:root { + --blue: #007bff; + --indigo: #6610f2; + --purple: #6f42c1; + --pink: #e83e8c; + --red: #dc3545; + --orange: #fd7e14; + --yellow: #ffc107; + --green: #28a745; + --teal: #20c997; + --cyan: #17a2b8; + --white: #fff; + --gray: #6c757d; + --gray-dark: #343a40; + --primary: #3A9ABF; + --secondary: #6C757D; + --success: #75CC39; + --info: #17a2b8; + --warning: #FDC02E; + --danger: #D93749; + --light: #f8f9fa; + --dark: #343a40; + --breakpoint-xs: 0; + --breakpoint-sm: 576px; + --breakpoint-md: 768px; + --breakpoint-lg: 992px; + --breakpoint-xl: 1200px; + --font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } + +*, +*::before, +*::after { + box-sizing: border-box; } + +html { + font-family: sans-serif; + line-height: 1.15; + -webkit-text-size-adjust: 100%; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } + +article, aside, figcaption, figure, footer, header, hgroup, main, nav, section { + display: block; } + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-size: .80rem; + font-weight: 400; + line-height: 1.5; + color: #212529; + text-align: left; + background-color: #fff; } + +[tabindex="-1"]:focus { + outline: 0 !important; } + +hr { + box-sizing: content-box; + height: 0; + overflow: visible; } + +h1, h2, h3, h4, h5, h6 { + margin-top: 0; + margin-bottom: 0; } + +p { + margin-top: 0; + margin-bottom: 1rem; } + +abbr[title], +abbr[data-original-title] { + text-decoration: underline; + text-decoration: underline dotted; + cursor: help; + border-bottom: 0; + text-decoration-skip-ink: none; } + +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit; } + +ol, +ul, +dl { + margin-top: 0; + margin-bottom: 1rem; } + +ol ol, +ul ul, +ol ul, +ul ol { + margin-bottom: 0; } + +dt { + font-weight: 700; } + +dd { + margin-bottom: .5rem; + margin-left: 0; } + +blockquote { + margin: 0 0 1rem; } + +b, +strong { + font-weight: bolder; } + +small { + font-size: 80%; } + +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; } + +sub { + bottom: -.25em; } + +sup { + top: -.5em; } + +a { + color: #3A9ABF; + text-decoration: none; + background-color: transparent; } + a:hover { + color: #286b84; + text-decoration: underline; } + +a:not([href]):not([tabindex]) { + color: inherit; + text-decoration: none; } + a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus { + color: inherit; + text-decoration: none; } + a:not([href]):not([tabindex]):focus { + outline: 0; } + +pre, +code, +kbd, +samp { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-size: 1em; } + +pre { + margin-top: 0; + margin-bottom: 1rem; + overflow: auto; } + +figure { + margin: 0 0 1rem; } + +img { + vertical-align: middle; + border-style: none; } + +svg { + overflow: hidden; + vertical-align: middle; } + +table { + border-collapse: collapse; } + +caption { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + color: #6c757d; + text-align: left; + caption-side: bottom; } + +th { + text-align: inherit; } + +label { + display: inline-block; + margin-bottom: 0.5rem; } + +button { + border-radius: 0; } + +button:focus { + outline: 1px dotted; + outline: 5px auto -webkit-focus-ring-color; } + +input, +button, +select, +optgroup, +textarea { + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit; } + +button, +input { + overflow: visible; } + +button, +select { + text-transform: none; } + +select { + word-wrap: normal; } + +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; } + +button:not(:disabled), +[type="button"]:not(:disabled), +[type="reset"]:not(:disabled), +[type="submit"]:not(:disabled) { + cursor: pointer; } + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + padding: 0; + border-style: none; } + +input[type="radio"], +input[type="checkbox"] { + box-sizing: border-box; + padding: 0; } + +input[type="date"], +input[type="time"], +input[type="datetime-local"], +input[type="month"] { + -webkit-appearance: listbox; } + +textarea { + overflow: auto; + resize: vertical; } + +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; } + +legend { + display: block; + width: 100%; + max-width: 100%; + padding: 0; + margin-bottom: .5rem; + font-size: 1.5rem; + line-height: inherit; + color: inherit; + white-space: normal; } + +progress { + vertical-align: baseline; } + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; } + +[type="search"] { + outline-offset: -2px; + -webkit-appearance: none; } + +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; } + +::-webkit-file-upload-button { + font: inherit; + -webkit-appearance: button; } + +output { + display: inline-block; } + +summary { + display: list-item; + cursor: pointer; } + +template { + display: none; } + +[hidden] { + display: none !important; } + +h1, h2, h3, h4, h5, h6, +.h1, .h2, .h3, .h4, .h5, .h6 { + margin-bottom: 0; + font-weight: 500; + line-height: 1.2; } + +h1, .h1 { + font-size: 2rem; } + +h2, .h2 { + font-size: 1.5rem; } + +h3, .h3 { + font-size: 1.25rem; } + +h4, .h4 { + font-size: 1rem; } + +h5, .h5 { + font-size: .85rem; } + +h6, .h6 { + font-size: .5rem; } + +.lead { + font-size: 1.25rem; + font-weight: 300; } + +.display-1 { + font-size: 6rem; + font-weight: 300; + line-height: 1.2; } + +.display-2 { + font-size: 5.5rem; + font-weight: 300; + line-height: 1.2; } + +.display-3 { + font-size: 4.5rem; + font-weight: 300; + line-height: 1.2; } + +.display-4 { + font-size: 3.5rem; + font-weight: 300; + line-height: 1.2; } + +hr { + margin-top: 1rem; + margin-bottom: 1rem; + border: 0; + border-top: 1px solid rgba(0, 0, 0, 0.1); } + +small, +.small { + font-size: 80%; + font-weight: 400; } + +mark, +.mark { + padding: 0.2em; + background-color: #fcf8e3; } + +.list-unstyled { + padding-left: 0; + list-style: none; } + +.list-inline { + padding-left: 0; + list-style: none; } + +.list-inline-item { + display: inline-block; } + .list-inline-item:not(:last-child) { + margin-right: 0.5rem; } + +.initialism { + font-size: 90%; + text-transform: uppercase; } + +.blockquote { + margin-bottom: 1rem; + font-size: 1.25rem; } + +.blockquote-footer { + display: block; + font-size: 80%; + color: #6c757d; } + .blockquote-footer::before { + content: "\2014\00A0"; } + +.img-fluid { + max-width: 100%; + height: auto; } + +.img-thumbnail { + padding: 0.25rem; + background-color: #fff; + border: 1px solid #dee2e6; + border-radius: 0.25rem; + max-width: 100%; + height: auto; } + +.figure { + display: inline-block; } + +.figure-img { + margin-bottom: 0.5rem; + line-height: 1; } + +.figure-caption { + font-size: 90%; + color: #6c757d; } + +code { + font-size: 87.5%; + color: #e83e8c; + word-break: break-word; } + a > code { + color: inherit; } + +kbd { + padding: 0.2rem 0.4rem; + font-size: 87.5%; + color: #fff; + background-color: #212529; + border-radius: 0.2rem; } + kbd kbd { + padding: 0; + font-size: 100%; + font-weight: 700; } + +pre { + display: block; + font-size: 87.5%; + color: #212529; } + pre code { + font-size: inherit; + color: inherit; + word-break: normal; } + +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; } + +.container { + width: 100%; + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; } + @media (min-width: 576px) { + .container { + max-width: 540px; } } + @media (min-width: 768px) { + .container { + max-width: 720px; } } + @media (min-width: 992px) { + .container { + max-width: 960px; } } + @media (min-width: 1200px) { + .container { + max-width: 1140px; } } + +.container-fluid { + width: 100%; + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; } + +.row { + display: flex; + flex-wrap: wrap; + margin-right: -15px; + margin-left: -15px; } + +.no-gutters { + margin-right: 0; + margin-left: 0; } + .no-gutters > .col, + .no-gutters > [class*="col-"] { + padding-right: 0; + padding-left: 0; } + +.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, +.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm, +.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md, +.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg, +.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl, +.col-xl-auto { + position: relative; + width: 100%; + padding-right: 15px; + padding-left: 15px; } + +.col { + flex-basis: 0; + flex-grow: 1; + max-width: 100%; } + +.col-auto { + flex: 0 0 auto; + width: auto; + max-width: 100%; } + +.col-1 { + flex: 0 0 8.3333333333%; + max-width: 8.3333333333%; } + +.col-2 { + flex: 0 0 16.6666666667%; + max-width: 16.6666666667%; } + +.col-3 { + flex: 0 0 25%; + max-width: 25%; } + +.col-4 { + flex: 0 0 33.3333333333%; + max-width: 33.3333333333%; } + +.col-5 { + flex: 0 0 41.6666666667%; + max-width: 41.6666666667%; } + +.col-6 { + flex: 0 0 50%; + max-width: 50%; } + +.col-7 { + flex: 0 0 58.3333333333%; + max-width: 58.3333333333%; } + +.col-8 { + flex: 0 0 66.6666666667%; + max-width: 66.6666666667%; } + +.col-9 { + flex: 0 0 75%; + max-width: 75%; } + +.col-10 { + flex: 0 0 83.3333333333%; + max-width: 83.3333333333%; } + +.col-11 { + flex: 0 0 91.6666666667%; + max-width: 91.6666666667%; } + +.col-12 { + flex: 0 0 100%; + max-width: 100%; } + +.order-first { + order: -1; } + +.order-last { + order: 13; } + +.order-0 { + order: 0; } + +.order-1 { + order: 1; } + +.order-2 { + order: 2; } + +.order-3 { + order: 3; } + +.order-4 { + order: 4; } + +.order-5 { + order: 5; } + +.order-6 { + order: 6; } + +.order-7 { + order: 7; } + +.order-8 { + order: 8; } + +.order-9 { + order: 9; } + +.order-10 { + order: 10; } + +.order-11 { + order: 11; } + +.order-12 { + order: 12; } + +.offset-1 { + margin-left: 8.3333333333%; } + +.offset-2 { + margin-left: 16.6666666667%; } + +.offset-3 { + margin-left: 25%; } + +.offset-4 { + margin-left: 33.3333333333%; } + +.offset-5 { + margin-left: 41.6666666667%; } + +.offset-6 { + margin-left: 50%; } + +.offset-7 { + margin-left: 58.3333333333%; } + +.offset-8 { + margin-left: 66.6666666667%; } + +.offset-9 { + margin-left: 75%; } + +.offset-10 { + margin-left: 83.3333333333%; } + +.offset-11 { + margin-left: 91.6666666667%; } + +@media (min-width: 576px) { + .col-sm { + flex-basis: 0; + flex-grow: 1; + max-width: 100%; } + + .col-sm-auto { + flex: 0 0 auto; + width: auto; + max-width: 100%; } + + .col-sm-1 { + flex: 0 0 8.3333333333%; + max-width: 8.3333333333%; } + + .col-sm-2 { + flex: 0 0 16.6666666667%; + max-width: 16.6666666667%; } + + .col-sm-3 { + flex: 0 0 25%; + max-width: 25%; } + + .col-sm-4 { + flex: 0 0 33.3333333333%; + max-width: 33.3333333333%; } + + .col-sm-5 { + flex: 0 0 41.6666666667%; + max-width: 41.6666666667%; } + + .col-sm-6 { + flex: 0 0 50%; + max-width: 50%; } + + .col-sm-7 { + flex: 0 0 58.3333333333%; + max-width: 58.3333333333%; } + + .col-sm-8 { + flex: 0 0 66.6666666667%; + max-width: 66.6666666667%; } + + .col-sm-9 { + flex: 0 0 75%; + max-width: 75%; } + + .col-sm-10 { + flex: 0 0 83.3333333333%; + max-width: 83.3333333333%; } + + .col-sm-11 { + flex: 0 0 91.6666666667%; + max-width: 91.6666666667%; } + + .col-sm-12 { + flex: 0 0 100%; + max-width: 100%; } + + .order-sm-first { + order: -1; } + + .order-sm-last { + order: 13; } + + .order-sm-0 { + order: 0; } + + .order-sm-1 { + order: 1; } + + .order-sm-2 { + order: 2; } + + .order-sm-3 { + order: 3; } + + .order-sm-4 { + order: 4; } + + .order-sm-5 { + order: 5; } + + .order-sm-6 { + order: 6; } + + .order-sm-7 { + order: 7; } + + .order-sm-8 { + order: 8; } + + .order-sm-9 { + order: 9; } + + .order-sm-10 { + order: 10; } + + .order-sm-11 { + order: 11; } + + .order-sm-12 { + order: 12; } + + .offset-sm-0 { + margin-left: 0; } + + .offset-sm-1 { + margin-left: 8.3333333333%; } + + .offset-sm-2 { + margin-left: 16.6666666667%; } + + .offset-sm-3 { + margin-left: 25%; } + + .offset-sm-4 { + margin-left: 33.3333333333%; } + + .offset-sm-5 { + margin-left: 41.6666666667%; } + + .offset-sm-6 { + margin-left: 50%; } + + .offset-sm-7 { + margin-left: 58.3333333333%; } + + .offset-sm-8 { + margin-left: 66.6666666667%; } + + .offset-sm-9 { + margin-left: 75%; } + + .offset-sm-10 { + margin-left: 83.3333333333%; } + + .offset-sm-11 { + margin-left: 91.6666666667%; } } +@media (min-width: 768px) { + .col-md { + flex-basis: 0; + flex-grow: 1; + max-width: 100%; } + + .col-md-auto { + flex: 0 0 auto; + width: auto; + max-width: 100%; } + + .col-md-1 { + flex: 0 0 8.3333333333%; + max-width: 8.3333333333%; } + + .col-md-2 { + flex: 0 0 16.6666666667%; + max-width: 16.6666666667%; } + + .col-md-3 { + flex: 0 0 25%; + max-width: 25%; } + + .col-md-4 { + flex: 0 0 33.3333333333%; + max-width: 33.3333333333%; } + + .col-md-5 { + flex: 0 0 41.6666666667%; + max-width: 41.6666666667%; } + + .col-md-6 { + flex: 0 0 50%; + max-width: 50%; } + + .col-md-7 { + flex: 0 0 58.3333333333%; + max-width: 58.3333333333%; } + + .col-md-8 { + flex: 0 0 66.6666666667%; + max-width: 66.6666666667%; } + + .col-md-9 { + flex: 0 0 75%; + max-width: 75%; } + + .col-md-10 { + flex: 0 0 83.3333333333%; + max-width: 83.3333333333%; } + + .col-md-11 { + flex: 0 0 91.6666666667%; + max-width: 91.6666666667%; } + + .col-md-12 { + flex: 0 0 100%; + max-width: 100%; } + + .order-md-first { + order: -1; } + + .order-md-last { + order: 13; } + + .order-md-0 { + order: 0; } + + .order-md-1 { + order: 1; } + + .order-md-2 { + order: 2; } + + .order-md-3 { + order: 3; } + + .order-md-4 { + order: 4; } + + .order-md-5 { + order: 5; } + + .order-md-6 { + order: 6; } + + .order-md-7 { + order: 7; } + + .order-md-8 { + order: 8; } + + .order-md-9 { + order: 9; } + + .order-md-10 { + order: 10; } + + .order-md-11 { + order: 11; } + + .order-md-12 { + order: 12; } + + .offset-md-0 { + margin-left: 0; } + + .offset-md-1 { + margin-left: 8.3333333333%; } + + .offset-md-2 { + margin-left: 16.6666666667%; } + + .offset-md-3 { + margin-left: 25%; } + + .offset-md-4 { + margin-left: 33.3333333333%; } + + .offset-md-5 { + margin-left: 41.6666666667%; } + + .offset-md-6 { + margin-left: 50%; } + + .offset-md-7 { + margin-left: 58.3333333333%; } + + .offset-md-8 { + margin-left: 66.6666666667%; } + + .offset-md-9 { + margin-left: 75%; } + + .offset-md-10 { + margin-left: 83.3333333333%; } + + .offset-md-11 { + margin-left: 91.6666666667%; } } +@media (min-width: 992px) { + .col-lg { + flex-basis: 0; + flex-grow: 1; + max-width: 100%; } + + .col-lg-auto { + flex: 0 0 auto; + width: auto; + max-width: 100%; } + + .col-lg-1 { + flex: 0 0 8.3333333333%; + max-width: 8.3333333333%; } + + .col-lg-2 { + flex: 0 0 16.6666666667%; + max-width: 16.6666666667%; } + + .col-lg-3 { + flex: 0 0 25%; + max-width: 25%; } + + .col-lg-4 { + flex: 0 0 33.3333333333%; + max-width: 33.3333333333%; } + + .col-lg-5 { + flex: 0 0 41.6666666667%; + max-width: 41.6666666667%; } + + .col-lg-6 { + flex: 0 0 50%; + max-width: 50%; } + + .col-lg-7 { + flex: 0 0 58.3333333333%; + max-width: 58.3333333333%; } + + .col-lg-8 { + flex: 0 0 66.6666666667%; + max-width: 66.6666666667%; } + + .col-lg-9 { + flex: 0 0 75%; + max-width: 75%; } + + .col-lg-10 { + flex: 0 0 83.3333333333%; + max-width: 83.3333333333%; } + + .col-lg-11 { + flex: 0 0 91.6666666667%; + max-width: 91.6666666667%; } + + .col-lg-12 { + flex: 0 0 100%; + max-width: 100%; } + + .order-lg-first { + order: -1; } + + .order-lg-last { + order: 13; } + + .order-lg-0 { + order: 0; } + + .order-lg-1 { + order: 1; } + + .order-lg-2 { + order: 2; } + + .order-lg-3 { + order: 3; } + + .order-lg-4 { + order: 4; } + + .order-lg-5 { + order: 5; } + + .order-lg-6 { + order: 6; } + + .order-lg-7 { + order: 7; } + + .order-lg-8 { + order: 8; } + + .order-lg-9 { + order: 9; } + + .order-lg-10 { + order: 10; } + + .order-lg-11 { + order: 11; } + + .order-lg-12 { + order: 12; } + + .offset-lg-0 { + margin-left: 0; } + + .offset-lg-1 { + margin-left: 8.3333333333%; } + + .offset-lg-2 { + margin-left: 16.6666666667%; } + + .offset-lg-3 { + margin-left: 25%; } + + .offset-lg-4 { + margin-left: 33.3333333333%; } + + .offset-lg-5 { + margin-left: 41.6666666667%; } + + .offset-lg-6 { + margin-left: 50%; } + + .offset-lg-7 { + margin-left: 58.3333333333%; } + + .offset-lg-8 { + margin-left: 66.6666666667%; } + + .offset-lg-9 { + margin-left: 75%; } + + .offset-lg-10 { + margin-left: 83.3333333333%; } + + .offset-lg-11 { + margin-left: 91.6666666667%; } } +@media (min-width: 1200px) { + .col-xl { + flex-basis: 0; + flex-grow: 1; + max-width: 100%; } + + .col-xl-auto { + flex: 0 0 auto; + width: auto; + max-width: 100%; } + + .col-xl-1 { + flex: 0 0 8.3333333333%; + max-width: 8.3333333333%; } + + .col-xl-2 { + flex: 0 0 16.6666666667%; + max-width: 16.6666666667%; } + + .col-xl-3 { + flex: 0 0 25%; + max-width: 25%; } + + .col-xl-4 { + flex: 0 0 33.3333333333%; + max-width: 33.3333333333%; } + + .col-xl-5 { + flex: 0 0 41.6666666667%; + max-width: 41.6666666667%; } + + .col-xl-6 { + flex: 0 0 50%; + max-width: 50%; } + + .col-xl-7 { + flex: 0 0 58.3333333333%; + max-width: 58.3333333333%; } + + .col-xl-8 { + flex: 0 0 66.6666666667%; + max-width: 66.6666666667%; } + + .col-xl-9 { + flex: 0 0 75%; + max-width: 75%; } + + .col-xl-10 { + flex: 0 0 83.3333333333%; + max-width: 83.3333333333%; } + + .col-xl-11 { + flex: 0 0 91.6666666667%; + max-width: 91.6666666667%; } + + .col-xl-12 { + flex: 0 0 100%; + max-width: 100%; } + + .order-xl-first { + order: -1; } + + .order-xl-last { + order: 13; } + + .order-xl-0 { + order: 0; } + + .order-xl-1 { + order: 1; } + + .order-xl-2 { + order: 2; } + + .order-xl-3 { + order: 3; } + + .order-xl-4 { + order: 4; } + + .order-xl-5 { + order: 5; } + + .order-xl-6 { + order: 6; } + + .order-xl-7 { + order: 7; } + + .order-xl-8 { + order: 8; } + + .order-xl-9 { + order: 9; } + + .order-xl-10 { + order: 10; } + + .order-xl-11 { + order: 11; } + + .order-xl-12 { + order: 12; } + + .offset-xl-0 { + margin-left: 0; } + + .offset-xl-1 { + margin-left: 8.3333333333%; } + + .offset-xl-2 { + margin-left: 16.6666666667%; } + + .offset-xl-3 { + margin-left: 25%; } + + .offset-xl-4 { + margin-left: 33.3333333333%; } + + .offset-xl-5 { + margin-left: 41.6666666667%; } + + .offset-xl-6 { + margin-left: 50%; } + + .offset-xl-7 { + margin-left: 58.3333333333%; } + + .offset-xl-8 { + margin-left: 66.6666666667%; } + + .offset-xl-9 { + margin-left: 75%; } + + .offset-xl-10 { + margin-left: 83.3333333333%; } + + .offset-xl-11 { + margin-left: 91.6666666667%; } } +.table { + width: 100%; + margin-bottom: 1rem; + color: #212529; } + .table th, + .table td { + padding: 0.75rem; + vertical-align: top; + border-top: 1px solid #dee2e6; } + .table thead th { + vertical-align: bottom; + border-bottom: 2px solid #dee2e6; } + .table tbody + tbody { + border-top: 2px solid #dee2e6; } + +.table-sm th, +.table-sm td { + padding: 0.3rem; } + +.table-bordered { + border: 1px solid #dee2e6; } + .table-bordered th, + .table-bordered td { + border: 1px solid #dee2e6; } + .table-bordered thead th, + .table-bordered thead td { + border-bottom-width: 2px; } + +.table-borderless th, +.table-borderless td, +.table-borderless thead th, +.table-borderless tbody + tbody { + border: 0; } + +.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(0, 0, 0, 0.05); } + +.table-hover tbody tr:hover { + color: #212529; + background-color: rgba(0, 0, 0, 0.075); } + +.table-primary, +.table-primary > th, +.table-primary > td { + background-color: #c8e3ed; } +.table-primary th, +.table-primary td, +.table-primary thead th, +.table-primary tbody + tbody { + border-color: #99cade; } + +.table-hover .table-primary:hover { + background-color: #b5d9e7; } + .table-hover .table-primary:hover > td, + .table-hover .table-primary:hover > th { + background-color: #b5d9e7; } + +.table-secondary, +.table-secondary > th, +.table-secondary > td { + background-color: #d6d8db; } +.table-secondary th, +.table-secondary td, +.table-secondary thead th, +.table-secondary tbody + tbody { + border-color: #b3b7bb; } + +.table-hover .table-secondary:hover { + background-color: #c8cbcf; } + .table-hover .table-secondary:hover > td, + .table-hover .table-secondary:hover > th { + background-color: #c8cbcf; } + +.table-success, +.table-success > th, +.table-success > td { + background-color: #d8f1c8; } +.table-success th, +.table-success td, +.table-success thead th, +.table-success tbody + tbody { + border-color: #b7e498; } + +.table-hover .table-success:hover { + background-color: #caecb4; } + .table-hover .table-success:hover > td, + .table-hover .table-success:hover > th { + background-color: #caecb4; } + +.table-info, +.table-info > th, +.table-info > td { + background-color: #bee5eb; } +.table-info th, +.table-info td, +.table-info thead th, +.table-info tbody + tbody { + border-color: #86cfda; } + +.table-hover .table-info:hover { + background-color: #abdde5; } + .table-hover .table-info:hover > td, + .table-hover .table-info:hover > th { + background-color: #abdde5; } + +.table-warning, +.table-warning > th, +.table-warning > td { + background-color: #feedc4; } +.table-warning th, +.table-warning td, +.table-warning thead th, +.table-warning tbody + tbody { + border-color: #fede92; } + +.table-hover .table-warning:hover { + background-color: #fee5ab; } + .table-hover .table-warning:hover > td, + .table-hover .table-warning:hover > th { + background-color: #fee5ab; } + +.table-danger, +.table-danger > th, +.table-danger > td { + background-color: #f4c7cc; } +.table-danger th, +.table-danger td, +.table-danger thead th, +.table-danger tbody + tbody { + border-color: #eb97a0; } + +.table-hover .table-danger:hover { + background-color: #f0b2b9; } + .table-hover .table-danger:hover > td, + .table-hover .table-danger:hover > th { + background-color: #f0b2b9; } + +.table-light, +.table-light > th, +.table-light > td { + background-color: #fdfdfe; } +.table-light th, +.table-light td, +.table-light thead th, +.table-light tbody + tbody { + border-color: #fbfcfc; } + +.table-hover .table-light:hover { + background-color: #ececf6; } + .table-hover .table-light:hover > td, + .table-hover .table-light:hover > th { + background-color: #ececf6; } + +.table-dark, +.table-dark > th, +.table-dark > td { + background-color: #c6c8ca; } +.table-dark th, +.table-dark td, +.table-dark thead th, +.table-dark tbody + tbody { + border-color: #95999c; } + +.table-hover .table-dark:hover { + background-color: #b9bbbe; } + .table-hover .table-dark:hover > td, + .table-hover .table-dark:hover > th { + background-color: #b9bbbe; } + +.table-active, +.table-active > th, +.table-active > td { + background-color: rgba(0, 0, 0, 0.075); } + +.table-hover .table-active:hover { + background-color: rgba(0, 0, 0, 0.075); } + .table-hover .table-active:hover > td, + .table-hover .table-active:hover > th { + background-color: rgba(0, 0, 0, 0.075); } + +.table .thead-dark th { + color: #fff; + background-color: #343a40; + border-color: #454d55; } +.table .thead-light th { + color: #495057; + background-color: #e9ecef; + border-color: #dee2e6; } + +.table-dark { + color: #fff; + background-color: #343a40; } + .table-dark th, + .table-dark td, + .table-dark thead th { + border-color: #454d55; } + .table-dark.table-bordered { + border: 0; } + .table-dark.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(255, 255, 255, 0.05); } + .table-dark.table-hover tbody tr:hover { + color: #fff; + background-color: rgba(255, 255, 255, 0.075); } + +@media (max-width: 575.98px) { + .table-responsive-sm { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; } + .table-responsive-sm > .table-bordered { + border: 0; } } +@media (max-width: 767.98px) { + .table-responsive-md { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; } + .table-responsive-md > .table-bordered { + border: 0; } } +@media (max-width: 991.98px) { + .table-responsive-lg { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; } + .table-responsive-lg > .table-bordered { + border: 0; } } +@media (max-width: 1199.98px) { + .table-responsive-xl { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; } + .table-responsive-xl > .table-bordered { + border: 0; } } +.table-responsive { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; } + .table-responsive > .table-bordered { + border: 0; } + +.form-control { + display: block; + width: 100%; + height: calc(1.5em + 0.75rem + 2px); + padding: 0.375rem 0.75rem; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #495057; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #ced4da; + border-radius: 0.25rem; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } + @media (prefers-reduced-motion: reduce) { + .form-control { + transition: none; } } + .form-control::-ms-expand { + background-color: transparent; + border: 0; } + .form-control:focus { + color: #495057; + background-color: #fff; + border-color: #99cce0; + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(58, 154, 191, 0.25); } + .form-control::placeholder { + color: #6c757d; + opacity: 1; } + .form-control:disabled, .form-control[readonly] { + background-color: #e9ecef; + opacity: 1; } + +select.form-control:focus::-ms-value { + color: #495057; + background-color: #fff; } + +.form-control-file, +.form-control-range { + display: block; + width: 100%; } + +.col-form-label { + padding-top: calc(0.375rem + 1px); + padding-bottom: calc(0.375rem + 1px); + margin-bottom: 0; + font-size: inherit; + line-height: 1.5; } + +.col-form-label-lg { + padding-top: calc(0.5rem + 1px); + padding-bottom: calc(0.5rem + 1px); + font-size: 1.25rem; + line-height: 1.5; } + +.col-form-label-sm { + padding-top: calc(0.25rem + 1px); + padding-bottom: calc(0.25rem + 1px); + font-size: 0.875rem; + line-height: 1.5; } + +.form-control-plaintext { + display: block; + width: 100%; + padding-top: 0.375rem; + padding-bottom: 0.375rem; + margin-bottom: 0; + line-height: 1.5; + color: #212529; + background-color: transparent; + border: solid transparent; + border-width: 1px 0; } + .form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg { + padding-right: 0; + padding-left: 0; } + +.form-control-sm { + height: calc(1.5em + 0.5rem + 2px); + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.2rem; } + +.form-control-lg { + height: calc(1.5em + 1rem + 2px); + padding: 0.5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: 0.3rem; } + +select.form-control[size], select.form-control[multiple] { + height: auto; } + +textarea.form-control { + height: auto; } + +.form-group { + margin-bottom: 1rem; } + +.form-text { + display: block; + margin-top: 0.25rem; } + +.form-row { + display: flex; + flex-wrap: wrap; + margin-right: -5px; + margin-left: -5px; } + .form-row > .col, + .form-row > [class*="col-"] { + padding-right: 5px; + padding-left: 5px; } + +.form-check { + position: relative; + display: block; + padding-left: 1.25rem; } + +.form-check-input { + position: absolute; + margin-top: 0.3rem; + margin-left: -1.25rem; } + .form-check-input:disabled ~ .form-check-label { + color: #6c757d; } + +.form-check-label { + margin-bottom: 0; } + +.form-check-inline { + display: inline-flex; + align-items: center; + padding-left: 0; + margin-right: 0.75rem; } + .form-check-inline .form-check-input { + position: static; + margin-top: 0; + margin-right: 0.3125rem; + margin-left: 0; } + +.valid-feedback { + display: none; + width: 100%; + margin-top: 0.25rem; + font-size: 80%; + color: #75CC39; } + +.valid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + max-width: 100%; + padding: 0.25rem 0.5rem; + margin-top: .1rem; + font-size: 0.875rem; + line-height: 1.5; + color: #212529; + background-color: rgba(117, 204, 57, 0.9); + border-radius: 0.25rem; } + +.was-validated .form-control:valid, .form-control.is-valid { + border-color: #75CC39; + padding-right: calc(1.5em + 0.75rem); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2375CC39' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: center right calc(0.375em + 0.1875rem); + background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); } + .was-validated .form-control:valid:focus, .form-control.is-valid:focus { + border-color: #75CC39; + box-shadow: 0 0 0 0.2rem rgba(117, 204, 57, 0.25); } + .was-validated .form-control:valid ~ .valid-feedback, + .was-validated .form-control:valid ~ .valid-tooltip, .form-control.is-valid ~ .valid-feedback, + .form-control.is-valid ~ .valid-tooltip { + display: block; } + +.was-validated textarea.form-control:valid, textarea.form-control.is-valid { + padding-right: calc(1.5em + 0.75rem); + background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem); } + +.was-validated .custom-select:valid, .custom-select.is-valid { + border-color: #75CC39; + padding-right: calc((1em + 0.75rem) * 3 / 4 + 1.75rem); + background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px, url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2375CC39' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); } + .was-validated .custom-select:valid:focus, .custom-select.is-valid:focus { + border-color: #75CC39; + box-shadow: 0 0 0 0.2rem rgba(117, 204, 57, 0.25); } + .was-validated .custom-select:valid ~ .valid-feedback, + .was-validated .custom-select:valid ~ .valid-tooltip, .custom-select.is-valid ~ .valid-feedback, + .custom-select.is-valid ~ .valid-tooltip { + display: block; } + +.was-validated .form-control-file:valid ~ .valid-feedback, +.was-validated .form-control-file:valid ~ .valid-tooltip, .form-control-file.is-valid ~ .valid-feedback, +.form-control-file.is-valid ~ .valid-tooltip { + display: block; } + +.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label { + color: #75CC39; } +.was-validated .form-check-input:valid ~ .valid-feedback, +.was-validated .form-check-input:valid ~ .valid-tooltip, .form-check-input.is-valid ~ .valid-feedback, +.form-check-input.is-valid ~ .valid-tooltip { + display: block; } + +.was-validated .custom-control-input:valid ~ .custom-control-label, .custom-control-input.is-valid ~ .custom-control-label { + color: #75CC39; } + .was-validated .custom-control-input:valid ~ .custom-control-label::before, .custom-control-input.is-valid ~ .custom-control-label::before { + border-color: #75CC39; } +.was-validated .custom-control-input:valid ~ .valid-feedback, +.was-validated .custom-control-input:valid ~ .valid-tooltip, .custom-control-input.is-valid ~ .valid-feedback, +.custom-control-input.is-valid ~ .valid-tooltip { + display: block; } +.was-validated .custom-control-input:valid:checked ~ .custom-control-label::before, .custom-control-input.is-valid:checked ~ .custom-control-label::before { + border-color: #91d662; + background-color: #91d662; } +.was-validated .custom-control-input:valid:focus ~ .custom-control-label::before, .custom-control-input.is-valid:focus ~ .custom-control-label::before { + box-shadow: 0 0 0 0.2rem rgba(117, 204, 57, 0.25); } +.was-validated .custom-control-input:valid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-valid:focus:not(:checked) ~ .custom-control-label::before { + border-color: #75CC39; } + +.was-validated .custom-file-input:valid ~ .custom-file-label, .custom-file-input.is-valid ~ .custom-file-label { + border-color: #75CC39; } +.was-validated .custom-file-input:valid ~ .valid-feedback, +.was-validated .custom-file-input:valid ~ .valid-tooltip, .custom-file-input.is-valid ~ .valid-feedback, +.custom-file-input.is-valid ~ .valid-tooltip { + display: block; } +.was-validated .custom-file-input:valid:focus ~ .custom-file-label, .custom-file-input.is-valid:focus ~ .custom-file-label { + border-color: #75CC39; + box-shadow: 0 0 0 0.2rem rgba(117, 204, 57, 0.25); } + +.invalid-feedback { + display: none; + width: 100%; + margin-top: 0.25rem; + font-size: 80%; + color: #D93749; } + +.invalid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + max-width: 100%; + padding: 0.25rem 0.5rem; + margin-top: .1rem; + font-size: 0.875rem; + line-height: 1.5; + color: #fff; + background-color: rgba(217, 55, 73, 0.9); + border-radius: 0.25rem; } + +.was-validated .form-control:invalid, .form-control.is-invalid { + border-color: #D93749; + padding-right: calc(1.5em + 0.75rem); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23D93749' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23D93749' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E"); + background-repeat: no-repeat; + background-position: center right calc(0.375em + 0.1875rem); + background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); } + .was-validated .form-control:invalid:focus, .form-control.is-invalid:focus { + border-color: #D93749; + box-shadow: 0 0 0 0.2rem rgba(217, 55, 73, 0.25); } + .was-validated .form-control:invalid ~ .invalid-feedback, + .was-validated .form-control:invalid ~ .invalid-tooltip, .form-control.is-invalid ~ .invalid-feedback, + .form-control.is-invalid ~ .invalid-tooltip { + display: block; } + +.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid { + padding-right: calc(1.5em + 0.75rem); + background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem); } + +.was-validated .custom-select:invalid, .custom-select.is-invalid { + border-color: #D93749; + padding-right: calc((1em + 0.75rem) * 3 / 4 + 1.75rem); + background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px, url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23D93749' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23D93749' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); } + .was-validated .custom-select:invalid:focus, .custom-select.is-invalid:focus { + border-color: #D93749; + box-shadow: 0 0 0 0.2rem rgba(217, 55, 73, 0.25); } + .was-validated .custom-select:invalid ~ .invalid-feedback, + .was-validated .custom-select:invalid ~ .invalid-tooltip, .custom-select.is-invalid ~ .invalid-feedback, + .custom-select.is-invalid ~ .invalid-tooltip { + display: block; } + +.was-validated .form-control-file:invalid ~ .invalid-feedback, +.was-validated .form-control-file:invalid ~ .invalid-tooltip, .form-control-file.is-invalid ~ .invalid-feedback, +.form-control-file.is-invalid ~ .invalid-tooltip { + display: block; } + +.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label { + color: #D93749; } +.was-validated .form-check-input:invalid ~ .invalid-feedback, +.was-validated .form-check-input:invalid ~ .invalid-tooltip, .form-check-input.is-invalid ~ .invalid-feedback, +.form-check-input.is-invalid ~ .invalid-tooltip { + display: block; } + +.was-validated .custom-control-input:invalid ~ .custom-control-label, .custom-control-input.is-invalid ~ .custom-control-label { + color: #D93749; } + .was-validated .custom-control-input:invalid ~ .custom-control-label::before, .custom-control-input.is-invalid ~ .custom-control-label::before { + border-color: #D93749; } +.was-validated .custom-control-input:invalid ~ .invalid-feedback, +.was-validated .custom-control-input:invalid ~ .invalid-tooltip, .custom-control-input.is-invalid ~ .invalid-feedback, +.custom-control-input.is-invalid ~ .invalid-tooltip { + display: block; } +.was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before, .custom-control-input.is-invalid:checked ~ .custom-control-label::before { + border-color: #e16270; + background-color: #e16270; } +.was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before, .custom-control-input.is-invalid:focus ~ .custom-control-label::before { + box-shadow: 0 0 0 0.2rem rgba(217, 55, 73, 0.25); } +.was-validated .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before { + border-color: #D93749; } + +.was-validated .custom-file-input:invalid ~ .custom-file-label, .custom-file-input.is-invalid ~ .custom-file-label { + border-color: #D93749; } +.was-validated .custom-file-input:invalid ~ .invalid-feedback, +.was-validated .custom-file-input:invalid ~ .invalid-tooltip, .custom-file-input.is-invalid ~ .invalid-feedback, +.custom-file-input.is-invalid ~ .invalid-tooltip { + display: block; } +.was-validated .custom-file-input:invalid:focus ~ .custom-file-label, .custom-file-input.is-invalid:focus ~ .custom-file-label { + border-color: #D93749; + box-shadow: 0 0 0 0.2rem rgba(217, 55, 73, 0.25); } + +.form-inline { + display: flex; + flex-flow: row wrap; + align-items: center; } + .form-inline .form-check { + width: 100%; } + @media (min-width: 576px) { + .form-inline label { + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 0; } + .form-inline .form-group { + display: flex; + flex: 0 0 auto; + flex-flow: row wrap; + align-items: center; + margin-bottom: 0; } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; } + .form-inline .form-control-plaintext { + display: inline-block; } + .form-inline .input-group, + .form-inline .custom-select { + width: auto; } + .form-inline .form-check { + display: flex; + align-items: center; + justify-content: center; + width: auto; + padding-left: 0; } + .form-inline .form-check-input { + position: relative; + flex-shrink: 0; + margin-top: 0; + margin-right: 0.25rem; + margin-left: 0; } + .form-inline .custom-control { + align-items: center; + justify-content: center; } + .form-inline .custom-control-label { + margin-bottom: 0; } } + +.btn { + cursor: pointer; + display: inline-block; + font-weight: 400; + color: #212529; + text-align: center; + vertical-align: middle; + user-select: none; + background-color: transparent; + border: 1px solid transparent; + padding: 0.375rem 0.75rem; + font-size: 1rem; + line-height: 1.5; + border-radius: 0.25rem; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } + @media (prefers-reduced-motion: reduce) { + .btn { + transition: none; } } + .btn:hover { + color: #212529; + text-decoration: none; } + .btn:focus, .btn.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(58, 154, 191, 0.25); } + .btn.disabled, .btn:disabled { + opacity: 0.65; } + +a.btn.disabled, +fieldset:disabled a.btn { + pointer-events: none; } + +.btn-primary { + color: #fff; + background-color: #3A9ABF; + border-color: #3A9ABF; } + .btn-primary:hover { + color: #fff; + background-color: #3182a2; + border-color: #2e7a98; } + .btn-primary:focus, .btn-primary.focus { + box-shadow: 0 0 0 0.2rem rgba(88, 169, 201, 0.5); } + .btn-primary.disabled, .btn-primary:disabled { + color: #fff; + background-color: #3A9ABF; + border-color: #3A9ABF; } + .btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active, .show > .btn-primary.dropdown-toggle { + color: #fff; + background-color: #2e7a98; + border-color: #2b738e; } + .btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus, .show > .btn-primary.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(88, 169, 201, 0.5); } + +.btn-secondary { + color: #fff; + background-color: #6C757D; + border-color: #6C757D; } + .btn-secondary:hover { + color: #fff; + background-color: #5a6268; + border-color: #545b62; } + .btn-secondary:focus, .btn-secondary.focus { + box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5); } + .btn-secondary.disabled, .btn-secondary:disabled { + color: #fff; + background-color: #6C757D; + border-color: #6C757D; } + .btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active, .show > .btn-secondary.dropdown-toggle { + color: #fff; + background-color: #545b62; + border-color: #4e555b; } + .btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus, .show > .btn-secondary.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5); } + +.btn-success { + color: #212529; + background-color: #75CC39; + border-color: #75CC39; } + .btn-success:hover { + color: #fff; + background-color: #63b12e; + border-color: #5ea72b; } + .btn-success:focus, .btn-success.focus { + box-shadow: 0 0 0 0.2rem rgba(104, 179, 55, 0.5); } + .btn-success.disabled, .btn-success:disabled { + color: #212529; + background-color: #75CC39; + border-color: #75CC39; } + .btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active, .show > .btn-success.dropdown-toggle { + color: #fff; + background-color: #5ea72b; + border-color: #589d28; } + .btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus, .show > .btn-success.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(104, 179, 55, 0.5); } + +.btn-info { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; } + .btn-info:hover { + color: #fff; + background-color: #138496; + border-color: #117a8b; } + .btn-info:focus, .btn-info.focus { + box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5); } + .btn-info.disabled, .btn-info:disabled { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; } + .btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active, .show > .btn-info.dropdown-toggle { + color: #fff; + background-color: #117a8b; + border-color: #10707f; } + .btn-info:not(:disabled):not(.disabled):active:focus, .btn-info:not(:disabled):not(.disabled).active:focus, .show > .btn-info.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5); } + +.btn-warning { + color: #212529; + background-color: #FDC02E; + border-color: #FDC02E; } + .btn-warning:hover { + color: #212529; + background-color: #fdb508; + border-color: #f6ae02; } + .btn-warning:focus, .btn-warning.focus { + box-shadow: 0 0 0 0.2rem rgba(220, 169, 45, 0.5); } + .btn-warning.disabled, .btn-warning:disabled { + color: #212529; + background-color: #FDC02E; + border-color: #FDC02E; } + .btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active, .show > .btn-warning.dropdown-toggle { + color: #212529; + background-color: #f6ae02; + border-color: #e9a502; } + .btn-warning:not(:disabled):not(.disabled):active:focus, .btn-warning:not(:disabled):not(.disabled).active:focus, .show > .btn-warning.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(220, 169, 45, 0.5); } + +.btn-danger { + color: #fff; + background-color: #D93749; + border-color: #D93749; } + .btn-danger:hover { + color: #fff; + background-color: #c42537; + border-color: #ba2334; } + .btn-danger:focus, .btn-danger.focus { + box-shadow: 0 0 0 0.2rem rgba(223, 85, 100, 0.5); } + .btn-danger.disabled, .btn-danger:disabled { + color: #fff; + background-color: #D93749; + border-color: #D93749; } + .btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active, .show > .btn-danger.dropdown-toggle { + color: #fff; + background-color: #ba2334; + border-color: #af2131; } + .btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus, .show > .btn-danger.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(223, 85, 100, 0.5); } + +.btn-light { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; } + .btn-light:hover { + color: #212529; + background-color: #e2e6ea; + border-color: #dae0e5; } + .btn-light:focus, .btn-light.focus { + box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5); } + .btn-light.disabled, .btn-light:disabled { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; } + .btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active, .show > .btn-light.dropdown-toggle { + color: #212529; + background-color: #dae0e5; + border-color: #d3d9df; } + .btn-light:not(:disabled):not(.disabled):active:focus, .btn-light:not(:disabled):not(.disabled).active:focus, .show > .btn-light.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5); } + +.btn-dark { + color: #fff; + background-color: #343a40; + border-color: #343a40; } + .btn-dark:hover { + color: #fff; + background-color: #23272b; + border-color: #1d2124; } + .btn-dark:focus, .btn-dark.focus { + box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5); } + .btn-dark.disabled, .btn-dark:disabled { + color: #fff; + background-color: #343a40; + border-color: #343a40; } + .btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active, .show > .btn-dark.dropdown-toggle { + color: #fff; + background-color: #1d2124; + border-color: #171a1d; } + .btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus, .show > .btn-dark.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5); } + +.btn-outline-primary { + color: #3A9ABF; + border-color: #3A9ABF; } + .btn-outline-primary:hover { + color: #fff; + background-color: #3A9ABF; + border-color: #3A9ABF; } + .btn-outline-primary:focus, .btn-outline-primary.focus { + box-shadow: 0 0 0 0.2rem rgba(58, 154, 191, 0.5); } + .btn-outline-primary.disabled, .btn-outline-primary:disabled { + color: #3A9ABF; + background-color: transparent; } + .btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active, .show > .btn-outline-primary.dropdown-toggle { + color: #fff; + background-color: #3A9ABF; + border-color: #3A9ABF; } + .btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-primary.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(58, 154, 191, 0.5); } + +.btn-outline-secondary { + color: #6C757D; + border-color: #6C757D; } + .btn-outline-secondary:hover { + color: #fff; + background-color: #6C757D; + border-color: #6C757D; } + .btn-outline-secondary:focus, .btn-outline-secondary.focus { + box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); } + .btn-outline-secondary.disabled, .btn-outline-secondary:disabled { + color: #6C757D; + background-color: transparent; } + .btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active, .show > .btn-outline-secondary.dropdown-toggle { + color: #fff; + background-color: #6C757D; + border-color: #6C757D; } + .btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-secondary.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); } + +.btn-outline-success { + color: #75CC39; + border-color: #75CC39; } + .btn-outline-success:hover { + color: #212529; + background-color: #75CC39; + border-color: #75CC39; } + .btn-outline-success:focus, .btn-outline-success.focus { + box-shadow: 0 0 0 0.2rem rgba(117, 204, 57, 0.5); } + .btn-outline-success.disabled, .btn-outline-success:disabled { + color: #75CC39; + background-color: transparent; } + .btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active, .show > .btn-outline-success.dropdown-toggle { + color: #212529; + background-color: #75CC39; + border-color: #75CC39; } + .btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-success.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(117, 204, 57, 0.5); } + +.btn-outline-info { + color: #17a2b8; + border-color: #17a2b8; } + .btn-outline-info:hover { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; } + .btn-outline-info:focus, .btn-outline-info.focus { + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); } + .btn-outline-info.disabled, .btn-outline-info:disabled { + color: #17a2b8; + background-color: transparent; } + .btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active, .show > .btn-outline-info.dropdown-toggle { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; } + .btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-info.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); } + +.btn-outline-warning { + color: #FDC02E; + border-color: #FDC02E; } + .btn-outline-warning:hover { + color: #212529; + background-color: #FDC02E; + border-color: #FDC02E; } + .btn-outline-warning:focus, .btn-outline-warning.focus { + box-shadow: 0 0 0 0.2rem rgba(253, 192, 46, 0.5); } + .btn-outline-warning.disabled, .btn-outline-warning:disabled { + color: #FDC02E; + background-color: transparent; } + .btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active, .show > .btn-outline-warning.dropdown-toggle { + color: #212529; + background-color: #FDC02E; + border-color: #FDC02E; } + .btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-warning.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(253, 192, 46, 0.5); } + +.btn-outline-danger { + color: #D93749; + border-color: #D93749; } + .btn-outline-danger:hover { + color: #fff; + background-color: #D93749; + border-color: #D93749; } + .btn-outline-danger:focus, .btn-outline-danger.focus { + box-shadow: 0 0 0 0.2rem rgba(217, 55, 73, 0.5); } + .btn-outline-danger.disabled, .btn-outline-danger:disabled { + color: #D93749; + background-color: transparent; } + .btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active, .show > .btn-outline-danger.dropdown-toggle { + color: #fff; + background-color: #D93749; + border-color: #D93749; } + .btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-danger.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(217, 55, 73, 0.5); } + +.btn-outline-light { + color: #f8f9fa; + border-color: #f8f9fa; } + .btn-outline-light:hover { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; } + .btn-outline-light:focus, .btn-outline-light.focus { + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); } + .btn-outline-light.disabled, .btn-outline-light:disabled { + color: #f8f9fa; + background-color: transparent; } + .btn-outline-light:not(:disabled):not(.disabled):active, .btn-outline-light:not(:disabled):not(.disabled).active, .show > .btn-outline-light.dropdown-toggle { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; } + .btn-outline-light:not(:disabled):not(.disabled):active:focus, .btn-outline-light:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-light.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); } + +.btn-outline-dark { + color: #343a40; + border-color: #343a40; } + .btn-outline-dark:hover { + color: #fff; + background-color: #343a40; + border-color: #343a40; } + .btn-outline-dark:focus, .btn-outline-dark.focus { + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); } + .btn-outline-dark.disabled, .btn-outline-dark:disabled { + color: #343a40; + background-color: transparent; } + .btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active, .show > .btn-outline-dark.dropdown-toggle { + color: #fff; + background-color: #343a40; + border-color: #343a40; } + .btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-dark.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); } + +.btn-link { + font-weight: 400; + color: #3A9ABF; + text-decoration: none; } + .btn-link:hover { + color: #286b84; + text-decoration: underline; } + .btn-link:focus, .btn-link.focus { + text-decoration: underline; + box-shadow: none; } + .btn-link:disabled, .btn-link.disabled { + color: #6c757d; + pointer-events: none; } + +.btn-lg, .btn-group-lg > .btn { + padding: 0.5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: 0.3rem; } + +.btn-sm, .btn-group-sm > .btn { + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + line-height: 1.5; + border-radius: 0.2rem; } + +.btn-block { + display: block; + width: 100%; } + .btn-block + .btn-block { + margin-top: 0.5rem; } + +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; } + +.fade { + transition: opacity 0.15s linear; } + @media (prefers-reduced-motion: reduce) { + .fade { + transition: none; } } + .fade:not(.show) { + opacity: 0; } + +.collapse:not(.show) { + display: none; } + +.collapsing { + position: relative; + height: 0; + overflow: hidden; + transition: height 0.35s ease; } + @media (prefers-reduced-motion: reduce) { + .collapsing { + transition: none; } } + +.dropup, +.dropright, +.dropdown, +.dropleft { + position: relative; } + +.dropdown-toggle { + white-space: nowrap; } + .dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid; + border-right: 0.3em solid transparent; + border-bottom: 0; + border-left: 0.3em solid transparent; } + .dropdown-toggle:empty::after { + margin-left: 0; } + +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 10rem; + padding: 0.5rem 0; + margin: 0.125rem 0 0; + font-size: 1rem; + color: #212529; + text-align: left; + list-style: none; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 0.25rem; } + +.dropdown-menu-left { + right: auto; + left: 0; } + +.dropdown-menu-right { + right: 0; + left: auto; } + +@media (min-width: 576px) { + .dropdown-menu-sm-left { + right: auto; + left: 0; } + + .dropdown-menu-sm-right { + right: 0; + left: auto; } } +@media (min-width: 768px) { + .dropdown-menu-md-left { + right: auto; + left: 0; } + + .dropdown-menu-md-right { + right: 0; + left: auto; } } +@media (min-width: 992px) { + .dropdown-menu-lg-left { + right: auto; + left: 0; } + + .dropdown-menu-lg-right { + right: 0; + left: auto; } } +@media (min-width: 1200px) { + .dropdown-menu-xl-left { + right: auto; + left: 0; } + + .dropdown-menu-xl-right { + right: 0; + left: auto; } } +.dropup .dropdown-menu { + top: auto; + bottom: 100%; + margin-top: 0; + margin-bottom: 0.125rem; } +.dropup .dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0; + border-right: 0.3em solid transparent; + border-bottom: 0.3em solid; + border-left: 0.3em solid transparent; } +.dropup .dropdown-toggle:empty::after { + margin-left: 0; } + +.dropright .dropdown-menu { + top: 0; + right: auto; + left: 100%; + margin-top: 0; + margin-left: 0.125rem; } +.dropright .dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid transparent; + border-right: 0; + border-bottom: 0.3em solid transparent; + border-left: 0.3em solid; } +.dropright .dropdown-toggle:empty::after { + margin-left: 0; } +.dropright .dropdown-toggle::after { + vertical-align: 0; } + +.dropleft .dropdown-menu { + top: 0; + right: 100%; + left: auto; + margin-top: 0; + margin-right: 0.125rem; } +.dropleft .dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; } +.dropleft .dropdown-toggle::after { + display: none; } +.dropleft .dropdown-toggle::before { + display: inline-block; + margin-right: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid transparent; + border-right: 0.3em solid; + border-bottom: 0.3em solid transparent; } +.dropleft .dropdown-toggle:empty::after { + margin-left: 0; } +.dropleft .dropdown-toggle::before { + vertical-align: 0; } + +.dropdown-menu[x-placement^="top"], .dropdown-menu[x-placement^="right"], .dropdown-menu[x-placement^="bottom"], .dropdown-menu[x-placement^="left"] { + right: auto; + bottom: auto; } + +.dropdown-divider { + height: 0; + margin: 0.5rem 0; + overflow: hidden; + border-top: 1px solid #e9ecef; } + +.dropdown-item { + display: block; + width: 100%; + padding: 0.25rem 1.5rem; + clear: both; + font-weight: 400; + color: #212529; + text-align: inherit; + white-space: nowrap; + background-color: transparent; + border: 0; } + .dropdown-item:hover, .dropdown-item:focus { + color: #16181b; + text-decoration: none; + background-color: #f8f9fa; } + .dropdown-item.active, .dropdown-item:active { + color: #fff; + text-decoration: none; + background-color: #3A9ABF; } + .dropdown-item.disabled, .dropdown-item:disabled { + color: #6c757d; + pointer-events: none; + background-color: transparent; } + +.dropdown-menu.show { + display: block; } + +.dropdown-header { + display: block; + padding: 0.5rem 1.5rem; + margin-bottom: 0; + font-size: 0.875rem; + color: #6c757d; + white-space: nowrap; } + +.dropdown-item-text { + display: block; + padding: 0.25rem 1.5rem; + color: #212529; } + +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-flex; + vertical-align: middle; } + .btn-group > .btn, + .btn-group-vertical > .btn { + position: relative; + flex: 1 1 auto; } + .btn-group > .btn:hover, + .btn-group-vertical > .btn:hover { + z-index: 1; } + .btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active, + .btn-group-vertical > .btn:focus, + .btn-group-vertical > .btn:active, + .btn-group-vertical > .btn.active { + z-index: 1; } + +.btn-toolbar { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; } + .btn-toolbar .input-group { + width: auto; } + +.btn-group > .btn:not(:first-child), +.btn-group > .btn-group:not(:first-child) { + margin-left: -1px; } +.btn-group > .btn:not(:last-child):not(.dropdown-toggle), +.btn-group > .btn-group:not(:last-child) > .btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0; } +.btn-group > .btn:not(:first-child), +.btn-group > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; } + +.dropdown-toggle-split { + padding-right: 0.5625rem; + padding-left: 0.5625rem; } + .dropdown-toggle-split::after, .dropup .dropdown-toggle-split::after, .dropright .dropdown-toggle-split::after { + margin-left: 0; } + .dropleft .dropdown-toggle-split::before { + margin-right: 0; } + +.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split { + padding-right: 0.375rem; + padding-left: 0.375rem; } + +.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split { + padding-right: 0.75rem; + padding-left: 0.75rem; } + +.btn-group-vertical { + flex-direction: column; + align-items: flex-start; + justify-content: center; } + .btn-group-vertical > .btn, + .btn-group-vertical > .btn-group { + width: 100%; } + .btn-group-vertical > .btn:not(:first-child), + .btn-group-vertical > .btn-group:not(:first-child) { + margin-top: -1px; } + .btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle), + .btn-group-vertical > .btn-group:not(:last-child) > .btn { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; } + .btn-group-vertical > .btn:not(:first-child), + .btn-group-vertical > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-top-right-radius: 0; } + +.btn-group-toggle > .btn, +.btn-group-toggle > .btn-group > .btn { + margin-bottom: 0; } + .btn-group-toggle > .btn input[type="radio"], + .btn-group-toggle > .btn input[type="checkbox"], + .btn-group-toggle > .btn-group > .btn input[type="radio"], + .btn-group-toggle > .btn-group > .btn input[type="checkbox"] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; } + +.input-group { + position: relative; + display: flex; + flex-wrap: wrap; + align-items: stretch; + width: 100%; } + .input-group > .form-control, + .input-group > .form-control-plaintext, + .input-group > .custom-select, + .input-group > .custom-file { + position: relative; + flex: 1 1 auto; + width: 1%; + margin-bottom: 0; } + .input-group > .form-control + .form-control, + .input-group > .form-control + .custom-select, + .input-group > .form-control + .custom-file, + .input-group > .form-control-plaintext + .form-control, + .input-group > .form-control-plaintext + .custom-select, + .input-group > .form-control-plaintext + .custom-file, + .input-group > .custom-select + .form-control, + .input-group > .custom-select + .custom-select, + .input-group > .custom-select + .custom-file, + .input-group > .custom-file + .form-control, + .input-group > .custom-file + .custom-select, + .input-group > .custom-file + .custom-file { + margin-left: -1px; } + .input-group > .form-control:focus, + .input-group > .custom-select:focus, + .input-group > .custom-file .custom-file-input:focus ~ .custom-file-label { + z-index: 3; } + .input-group > .custom-file .custom-file-input:focus { + z-index: 4; } + .input-group > .form-control:not(:last-child), + .input-group > .custom-select:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; } + .input-group > .form-control:not(:first-child), + .input-group > .custom-select:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; } + .input-group > .custom-file { + display: flex; + align-items: center; } + .input-group > .custom-file:not(:last-child) .custom-file-label, .input-group > .custom-file:not(:last-child) .custom-file-label::after { + border-top-right-radius: 0; + border-bottom-right-radius: 0; } + .input-group > .custom-file:not(:first-child) .custom-file-label { + border-top-left-radius: 0; + border-bottom-left-radius: 0; } + +.input-group-prepend, +.input-group-append { + display: flex; } + .input-group-prepend .btn, + .input-group-append .btn { + position: relative; + z-index: 2; } + .input-group-prepend .btn:focus, + .input-group-append .btn:focus { + z-index: 3; } + .input-group-prepend .btn + .btn, + .input-group-prepend .btn + .input-group-text, + .input-group-prepend .input-group-text + .input-group-text, + .input-group-prepend .input-group-text + .btn, + .input-group-append .btn + .btn, + .input-group-append .btn + .input-group-text, + .input-group-append .input-group-text + .input-group-text, + .input-group-append .input-group-text + .btn { + margin-left: -1px; } + +.input-group-prepend { + margin-right: -1px; } + +.input-group-append { + margin-left: -1px; } + +.input-group-text { + display: flex; + align-items: center; + padding: 0.375rem 0.75rem; + margin-bottom: 0; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #495057; + text-align: center; + white-space: nowrap; + background-color: #e9ecef; + border: 1px solid #ced4da; + border-radius: 0.25rem; } + .input-group-text input[type="radio"], + .input-group-text input[type="checkbox"] { + margin-top: 0; } + +.input-group-lg > .form-control:not(textarea), +.input-group-lg > .custom-select { + height: calc(1.5em + 1rem + 2px); } + +.input-group-lg > .form-control, +.input-group-lg > .custom-select, +.input-group-lg > .input-group-prepend > .input-group-text, +.input-group-lg > .input-group-append > .input-group-text, +.input-group-lg > .input-group-prepend > .btn, +.input-group-lg > .input-group-append > .btn { + padding: 0.5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: 0.3rem; } + +.input-group-sm > .form-control:not(textarea), +.input-group-sm > .custom-select { + height: calc(1.5em + 0.5rem + 2px); } + +.input-group-sm > .form-control, +.input-group-sm > .custom-select, +.input-group-sm > .input-group-prepend > .input-group-text, +.input-group-sm > .input-group-append > .input-group-text, +.input-group-sm > .input-group-prepend > .btn, +.input-group-sm > .input-group-append > .btn { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.2rem; } + +.input-group-lg > .custom-select, +.input-group-sm > .custom-select { + padding-right: 1.75rem; } + +.input-group > .input-group-prepend > .btn, +.input-group > .input-group-prepend > .input-group-text, +.input-group > .input-group-append:not(:last-child) > .btn, +.input-group > .input-group-append:not(:last-child) > .input-group-text, +.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group > .input-group-append:last-child > .input-group-text:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; } + +.input-group > .input-group-append > .btn, +.input-group > .input-group-append > .input-group-text, +.input-group > .input-group-prepend:not(:first-child) > .btn, +.input-group > .input-group-prepend:not(:first-child) > .input-group-text, +.input-group > .input-group-prepend:first-child > .btn:not(:first-child), +.input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; } + +.custom-control { + position: relative; + display: block; + min-height: 1.5rem; + padding-left: 1.5rem; } + +.custom-control-inline { + display: inline-flex; + margin-right: 1rem; } + +.custom-control-input { + position: absolute; + z-index: -1; + opacity: 0; } + .custom-control-input:checked ~ .custom-control-label::before { + color: #fff; + border-color: #3A9ABF; + background-color: #3A9ABF; } + .custom-control-input:focus ~ .custom-control-label::before { + box-shadow: 0 0 0 0.2rem rgba(58, 154, 191, 0.25); } + .custom-control-input:focus:not(:checked) ~ .custom-control-label::before { + border-color: #99cce0; } + .custom-control-input:not(:disabled):active ~ .custom-control-label::before { + color: #fff; + background-color: #c0e0ec; + border-color: #c0e0ec; } + .custom-control-input:disabled ~ .custom-control-label { + color: #6c757d; } + .custom-control-input:disabled ~ .custom-control-label::before { + background-color: #e9ecef; } + +.custom-control-label { + position: relative; + margin-bottom: 0; + vertical-align: top; } + .custom-control-label::before { + position: absolute; + top: 0.25rem; + left: -1.5rem; + display: block; + width: 1rem; + height: 1rem; + pointer-events: none; + content: ""; + background-color: #fff; + border: #adb5bd solid 1px; } + .custom-control-label::after { + position: absolute; + top: 0.25rem; + left: -1.5rem; + display: block; + width: 1rem; + height: 1rem; + content: ""; + background: no-repeat 50% / 50% 50%; } + +.custom-checkbox .custom-control-label::before { + border-radius: 0.25rem; } +.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e"); } +.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before { + border-color: #3A9ABF; + background-color: #3A9ABF; } +.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e"); } +.custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before { + background-color: rgba(58, 154, 191, 0.5); } +.custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before { + background-color: rgba(58, 154, 191, 0.5); } + +.custom-radio .custom-control-label::before { + border-radius: 50%; } +.custom-radio .custom-control-input:checked ~ .custom-control-label::after { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e"); } +.custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before { + background-color: rgba(58, 154, 191, 0.5); } + +.custom-switch { + padding-left: 2.25rem; } + .custom-switch .custom-control-label::before { + left: -2.25rem; + width: 1.75rem; + pointer-events: all; + border-radius: 0.5rem; } + .custom-switch .custom-control-label::after { + top: calc(0.25rem + 2px); + left: calc(-2.25rem + 2px); + width: calc(1rem - 4px); + height: calc(1rem - 4px); + background-color: #adb5bd; + border-radius: 0.5rem; + transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } + @media (prefers-reduced-motion: reduce) { + .custom-switch .custom-control-label::after { + transition: none; } } + .custom-switch .custom-control-input:checked ~ .custom-control-label::after { + background-color: #fff; + transform: translateX(0.75rem); } + .custom-switch .custom-control-input:disabled:checked ~ .custom-control-label::before { + background-color: rgba(58, 154, 191, 0.5); } + +.custom-select { + display: inline-block; + width: 100%; + height: calc(1.5em + 0.75rem + 2px); + padding: 0.375rem 1.75rem 0.375rem 0.75rem; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #495057; + vertical-align: middle; + background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px; + background-color: #fff; + border: 1px solid #ced4da; + border-radius: 0.25rem; + appearance: none; } + .custom-select:focus { + border-color: #99cce0; + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(58, 154, 191, 0.25); } + .custom-select:focus::-ms-value { + color: #495057; + background-color: #fff; } + .custom-select[multiple], .custom-select[size]:not([size="1"]) { + height: auto; + padding-right: 0.75rem; + background-image: none; } + .custom-select:disabled { + color: #6c757d; + background-color: #e9ecef; } + .custom-select::-ms-expand { + display: none; } + +.custom-select-sm { + height: calc(1.5em + 0.5rem + 2px); + padding-top: 0.25rem; + padding-bottom: 0.25rem; + padding-left: 0.5rem; + font-size: 0.875rem; } + +.custom-select-lg { + height: calc(1.5em + 1rem + 2px); + padding-top: 0.5rem; + padding-bottom: 0.5rem; + padding-left: 1rem; + font-size: 1.25rem; } + +.custom-file { + position: relative; + display: inline-block; + width: 100%; + height: calc(1.5em + 0.75rem + 2px); + margin-bottom: 0; } + +.custom-file-input { + position: relative; + z-index: 2; + width: 100%; + height: calc(1.5em + 0.75rem + 2px); + margin: 0; + opacity: 0; } + .custom-file-input:focus ~ .custom-file-label { + border-color: #99cce0; + box-shadow: 0 0 0 0.2rem rgba(58, 154, 191, 0.25); } + .custom-file-input:disabled ~ .custom-file-label { + background-color: #e9ecef; } + .custom-file-input:lang(en) ~ .custom-file-label::after { + content: "Browse"; } + .custom-file-input ~ .custom-file-label[data-browse]::after { + content: attr(data-browse); } + +.custom-file-label { + position: absolute; + top: 0; + right: 0; + left: 0; + z-index: 1; + height: calc(1.5em + 0.75rem + 2px); + padding: 0.375rem 0.75rem; + font-weight: 400; + line-height: 1.5; + color: #495057; + background-color: #fff; + border: 1px solid #ced4da; + border-radius: 0.25rem; } + .custom-file-label::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + z-index: 3; + display: block; + height: calc(1.5em + 0.75rem); + padding: 0.375rem 0.75rem; + line-height: 1.5; + color: #495057; + content: "Browse"; + background-color: #e9ecef; + border-left: inherit; + border-radius: 0 0.25rem 0.25rem 0; } + +.custom-range { + width: 100%; + height: calc(1rem + 0.4rem); + padding: 0; + background-color: transparent; + appearance: none; } + .custom-range:focus { + outline: none; } + .custom-range:focus::-webkit-slider-thumb { + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(58, 154, 191, 0.25); } + .custom-range:focus::-moz-range-thumb { + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(58, 154, 191, 0.25); } + .custom-range:focus::-ms-thumb { + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(58, 154, 191, 0.25); } + .custom-range::-moz-focus-outer { + border: 0; } + .custom-range::-webkit-slider-thumb { + width: 1rem; + height: 1rem; + margin-top: -0.25rem; + background-color: #3A9ABF; + border: 0; + border-radius: 1rem; + transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + appearance: none; } + @media (prefers-reduced-motion: reduce) { + .custom-range::-webkit-slider-thumb { + transition: none; } } + .custom-range::-webkit-slider-thumb:active { + background-color: #c0e0ec; } + .custom-range::-webkit-slider-runnable-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: #dee2e6; + border-color: transparent; + border-radius: 1rem; } + .custom-range::-moz-range-thumb { + width: 1rem; + height: 1rem; + background-color: #3A9ABF; + border: 0; + border-radius: 1rem; + transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + appearance: none; } + @media (prefers-reduced-motion: reduce) { + .custom-range::-moz-range-thumb { + transition: none; } } + .custom-range::-moz-range-thumb:active { + background-color: #c0e0ec; } + .custom-range::-moz-range-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: #dee2e6; + border-color: transparent; + border-radius: 1rem; } + .custom-range::-ms-thumb { + width: 1rem; + height: 1rem; + margin-top: 0; + margin-right: 0.2rem; + margin-left: 0.2rem; + background-color: #3A9ABF; + border: 0; + border-radius: 1rem; + transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + appearance: none; } + @media (prefers-reduced-motion: reduce) { + .custom-range::-ms-thumb { + transition: none; } } + .custom-range::-ms-thumb:active { + background-color: #c0e0ec; } + .custom-range::-ms-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: transparent; + border-color: transparent; + border-width: 0.5rem; } + .custom-range::-ms-fill-lower { + background-color: #dee2e6; + border-radius: 1rem; } + .custom-range::-ms-fill-upper { + margin-right: 15px; + background-color: #dee2e6; + border-radius: 1rem; } + .custom-range:disabled::-webkit-slider-thumb { + background-color: #adb5bd; } + .custom-range:disabled::-webkit-slider-runnable-track { + cursor: default; } + .custom-range:disabled::-moz-range-thumb { + background-color: #adb5bd; } + .custom-range:disabled::-moz-range-track { + cursor: default; } + .custom-range:disabled::-ms-thumb { + background-color: #adb5bd; } + +.custom-control-label::before, +.custom-file-label, +.custom-select { + transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } + @media (prefers-reduced-motion: reduce) { + .custom-control-label::before, + .custom-file-label, + .custom-select { + transition: none; } } + +.nav { + display: flex; + flex-wrap: wrap; + padding-left: 0; + margin-bottom: 0; + list-style: none; } + +.nav-link { + display: block; + padding: 0.5rem 1rem; } + .nav-link:hover, .nav-link:focus { + text-decoration: none; } + .nav-link.disabled { + color: #6c757d; + pointer-events: none; + cursor: default; } + +.nav-tabs { + border-bottom: 1px solid #dee2e6; } + .nav-tabs .nav-item { + margin-bottom: -1px; } + .nav-tabs .nav-link { + border: 1px solid transparent; + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; } + .nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus { + border-color: #e9ecef #e9ecef #dee2e6; } + .nav-tabs .nav-link.disabled { + color: #6c757d; + background-color: transparent; + border-color: transparent; } + .nav-tabs .nav-link.active, + .nav-tabs .nav-item.show .nav-link { + color: #495057; + background-color: #fff; + border-color: #dee2e6 #dee2e6 #fff; } + .nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-left-radius: 0; + border-top-right-radius: 0; } + +.nav-pills .nav-link { + border-radius: 0.25rem; } +.nav-pills .nav-link.active, +.nav-pills .show > .nav-link { + color: #fff; + background-color: #3A9ABF; } + +.nav-fill .nav-item { + flex: 1 1 auto; + text-align: center; } + +.nav-justified .nav-item { + flex-basis: 0; + flex-grow: 1; + text-align: center; } + +.tab-content > .tab-pane { + display: none; } +.tab-content > .active { + display: block; } + +.navbar { + position: relative; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + padding: 0.5rem 1rem; } + .navbar > .container, + .navbar > .container-fluid { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; } + +.navbar-brand { + display: inline-block; + padding-top: 0.3125rem; + padding-bottom: 0.3125rem; + margin-right: 1rem; + font-size: 1.25rem; + line-height: inherit; + white-space: nowrap; } + .navbar-brand:hover, .navbar-brand:focus { + text-decoration: none; } + +.navbar-nav { + display: flex; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + list-style: none; } + .navbar-nav .nav-link { + padding-right: 0; + padding-left: 0; } + .navbar-nav .dropdown-menu { + position: static; + float: none; } + +.navbar-text { + display: inline-block; + padding-top: 0.5rem; + padding-bottom: 0.5rem; } + +.navbar-collapse { + flex-basis: 100%; + flex-grow: 1; + align-items: center; } + +.navbar-toggler { + padding: 0.25rem 0.75rem; + font-size: 1.25rem; + line-height: 1; + background-color: transparent; + border: 1px solid transparent; + border-radius: 0.25rem; } + .navbar-toggler:hover, .navbar-toggler:focus { + text-decoration: none; } + +.navbar-toggler-icon { + display: inline-block; + width: 1.5em; + height: 1.5em; + vertical-align: middle; + content: ""; + background: no-repeat center center; + background-size: 100% 100%; } + +@media (max-width: 575.98px) { + .navbar-expand-sm > .container, + .navbar-expand-sm > .container-fluid { + padding-right: 0; + padding-left: 0; } } +@media (min-width: 576px) { + .navbar-expand-sm { + flex-flow: row nowrap; + justify-content: flex-start; } + .navbar-expand-sm .navbar-nav { + flex-direction: row; } + .navbar-expand-sm .navbar-nav .dropdown-menu { + position: absolute; } + .navbar-expand-sm .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; } + .navbar-expand-sm > .container, + .navbar-expand-sm > .container-fluid { + flex-wrap: nowrap; } + .navbar-expand-sm .navbar-collapse { + display: flex !important; + flex-basis: auto; } + .navbar-expand-sm .navbar-toggler { + display: none; } } +@media (max-width: 767.98px) { + .navbar-expand-md > .container, + .navbar-expand-md > .container-fluid { + padding-right: 0; + padding-left: 0; } } +@media (min-width: 768px) { + .navbar-expand-md { + flex-flow: row nowrap; + justify-content: flex-start; } + .navbar-expand-md .navbar-nav { + flex-direction: row; } + .navbar-expand-md .navbar-nav .dropdown-menu { + position: absolute; } + .navbar-expand-md .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; } + .navbar-expand-md > .container, + .navbar-expand-md > .container-fluid { + flex-wrap: nowrap; } + .navbar-expand-md .navbar-collapse { + display: flex !important; + flex-basis: auto; } + .navbar-expand-md .navbar-toggler { + display: none; } } +@media (max-width: 991.98px) { + .navbar-expand-lg > .container, + .navbar-expand-lg > .container-fluid { + padding-right: 0; + padding-left: 0; } } +@media (min-width: 992px) { + .navbar-expand-lg { + flex-flow: row nowrap; + justify-content: flex-start; } + .navbar-expand-lg .navbar-nav { + flex-direction: row; } + .navbar-expand-lg .navbar-nav .dropdown-menu { + position: absolute; } + .navbar-expand-lg .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; } + .navbar-expand-lg > .container, + .navbar-expand-lg > .container-fluid { + flex-wrap: nowrap; } + .navbar-expand-lg .navbar-collapse { + display: flex !important; + flex-basis: auto; } + .navbar-expand-lg .navbar-toggler { + display: none; } } +@media (max-width: 1199.98px) { + .navbar-expand-xl > .container, + .navbar-expand-xl > .container-fluid { + padding-right: 0; + padding-left: 0; } } +@media (min-width: 1200px) { + .navbar-expand-xl { + flex-flow: row nowrap; + justify-content: flex-start; } + .navbar-expand-xl .navbar-nav { + flex-direction: row; } + .navbar-expand-xl .navbar-nav .dropdown-menu { + position: absolute; } + .navbar-expand-xl .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; } + .navbar-expand-xl > .container, + .navbar-expand-xl > .container-fluid { + flex-wrap: nowrap; } + .navbar-expand-xl .navbar-collapse { + display: flex !important; + flex-basis: auto; } + .navbar-expand-xl .navbar-toggler { + display: none; } } +.navbar-expand { + flex-flow: row nowrap; + justify-content: flex-start; } + .navbar-expand > .container, + .navbar-expand > .container-fluid { + padding-right: 0; + padding-left: 0; } + .navbar-expand .navbar-nav { + flex-direction: row; } + .navbar-expand .navbar-nav .dropdown-menu { + position: absolute; } + .navbar-expand .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; } + .navbar-expand > .container, + .navbar-expand > .container-fluid { + flex-wrap: nowrap; } + .navbar-expand .navbar-collapse { + display: flex !important; + flex-basis: auto; } + .navbar-expand .navbar-toggler { + display: none; } + +.navbar-light .navbar-brand { + color: rgba(0, 0, 0, 0.9); } + .navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus { + color: rgba(0, 0, 0, 0.9); } +.navbar-light .navbar-nav .nav-link { + color: rgba(0, 0, 0, 0.5); } + .navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus { + color: rgba(0, 0, 0, 0.7); } + .navbar-light .navbar-nav .nav-link.disabled { + color: rgba(0, 0, 0, 0.3); } +.navbar-light .navbar-nav .show > .nav-link, +.navbar-light .navbar-nav .active > .nav-link, +.navbar-light .navbar-nav .nav-link.show, +.navbar-light .navbar-nav .nav-link.active { + color: rgba(0, 0, 0, 0.9); } +.navbar-light .navbar-toggler { + color: rgba(0, 0, 0, 0.5); + border-color: rgba(0, 0, 0, 0.1); } +.navbar-light .navbar-toggler-icon { + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); } +.navbar-light .navbar-text { + color: rgba(0, 0, 0, 0.5); } + .navbar-light .navbar-text a { + color: rgba(0, 0, 0, 0.9); } + .navbar-light .navbar-text a:hover, .navbar-light .navbar-text a:focus { + color: rgba(0, 0, 0, 0.9); } + +.navbar-dark .navbar-brand { + color: #fff; } + .navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus { + color: #fff; } +.navbar-dark .navbar-nav .nav-link { + color: rgba(255, 255, 255, 0.5); } + .navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus { + color: rgba(255, 255, 255, 0.75); } + .navbar-dark .navbar-nav .nav-link.disabled { + color: rgba(255, 255, 255, 0.25); } +.navbar-dark .navbar-nav .show > .nav-link, +.navbar-dark .navbar-nav .active > .nav-link, +.navbar-dark .navbar-nav .nav-link.show, +.navbar-dark .navbar-nav .nav-link.active { + color: #fff; } +.navbar-dark .navbar-toggler { + color: rgba(255, 255, 255, 0.5); + border-color: rgba(255, 255, 255, 0.1); } +.navbar-dark .navbar-toggler-icon { + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); } +.navbar-dark .navbar-text { + color: rgba(255, 255, 255, 0.5); } + .navbar-dark .navbar-text a { + color: #fff; } + .navbar-dark .navbar-text a:hover, .navbar-dark .navbar-text a:focus { + color: #fff; } + +.card { + position: relative; + display: flex; + flex-direction: column; + min-width: 0; + word-wrap: break-word; + background-color: #fff; + background-clip: border-box; + border: 1px solid rgba(0, 0, 0, 0.125); + border-radius: 0.25rem; } + .card > hr { + margin-right: 0; + margin-left: 0; } + .card > .list-group:first-child .list-group-item:first-child { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; } + .card > .list-group:last-child .list-group-item:last-child { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; } + +.card-body { + flex: 1 1 auto; + padding: 1.25rem; } + +.card-title { + margin-bottom: 0.75rem; } + +.card-subtitle { + margin-top: -0.375rem; + margin-bottom: 0; } + +.card-text:last-child { + margin-bottom: 0; } + +.card-link:hover { + text-decoration: none; } +.card-link + .card-link { + margin-left: 1.25rem; } + +.card-header { + padding: 0.75rem 1.25rem; + margin-bottom: 0; + background-color: rgba(0, 0, 0, 0.03); + border-bottom: 1px solid rgba(0, 0, 0, 0.125); } + .card-header:first-child { + border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0; } + .card-header + .list-group .list-group-item:first-child { + border-top: 0; } + +.card-footer { + padding: 0.75rem 1.25rem; + background-color: rgba(0, 0, 0, 0.03); + border-top: 1px solid rgba(0, 0, 0, 0.125); } + .card-footer:last-child { + border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px); } + +.card-header-tabs { + margin-right: -0.625rem; + margin-bottom: -0.75rem; + margin-left: -0.625rem; + border-bottom: 0; } + +.card-header-pills { + margin-right: -0.625rem; + margin-left: -0.625rem; } + +.card-img-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: 1.25rem; } + +.card-img { + width: 100%; + border-radius: calc(0.25rem - 1px); } + +.card-img-top { + width: 100%; + border-top-left-radius: calc(0.25rem - 1px); + border-top-right-radius: calc(0.25rem - 1px); } + +.card-img-bottom { + width: 100%; + border-bottom-right-radius: calc(0.25rem - 1px); + border-bottom-left-radius: calc(0.25rem - 1px); } + +.card-deck { + display: flex; + flex-direction: column; } + .card-deck .card { + margin-bottom: 15px; } + @media (min-width: 576px) { + .card-deck { + flex-flow: row wrap; + margin-right: -15px; + margin-left: -15px; } + .card-deck .card { + display: flex; + flex: 1 0 0%; + flex-direction: column; + margin-right: 15px; + margin-bottom: 0; + margin-left: 15px; } } + +.card-group { + display: flex; + flex-direction: column; } + .card-group > .card { + margin-bottom: 15px; } + @media (min-width: 576px) { + .card-group { + flex-flow: row wrap; } + .card-group > .card { + flex: 1 0 0%; + margin-bottom: 0; } + .card-group > .card + .card { + margin-left: 0; + border-left: 0; } + .card-group > .card:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; } + .card-group > .card:not(:last-child) .card-img-top, + .card-group > .card:not(:last-child) .card-header { + border-top-right-radius: 0; } + .card-group > .card:not(:last-child) .card-img-bottom, + .card-group > .card:not(:last-child) .card-footer { + border-bottom-right-radius: 0; } + .card-group > .card:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; } + .card-group > .card:not(:first-child) .card-img-top, + .card-group > .card:not(:first-child) .card-header { + border-top-left-radius: 0; } + .card-group > .card:not(:first-child) .card-img-bottom, + .card-group > .card:not(:first-child) .card-footer { + border-bottom-left-radius: 0; } } + +.card-columns .card { + margin-bottom: 0.75rem; } +@media (min-width: 576px) { + .card-columns { + column-count: 3; + column-gap: 1.25rem; + orphans: 1; + widows: 1; } + .card-columns .card { + display: inline-block; + width: 100%; } } + +.accordion > .card { + overflow: hidden; } + .accordion > .card:not(:first-of-type) .card-header:first-child { + border-radius: 0; } + .accordion > .card:not(:first-of-type):not(:last-of-type) { + border-bottom: 0; + border-radius: 0; } + .accordion > .card:first-of-type { + border-bottom: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; } + .accordion > .card:last-of-type { + border-top-left-radius: 0; + border-top-right-radius: 0; } + .accordion > .card .card-header { + margin-bottom: -1px; } + +.breadcrumb { + display: flex; + flex-wrap: wrap; + padding: 0.75rem 1rem; + margin-bottom: 1rem; + list-style: none; + background-color: #e9ecef; + border-radius: 0.25rem; } + +.breadcrumb-item + .breadcrumb-item { + padding-left: 0.5rem; } + .breadcrumb-item + .breadcrumb-item::before { + display: inline-block; + padding-right: 0.5rem; + color: #6c757d; + content: "/"; } +.breadcrumb-item + .breadcrumb-item:hover::before { + text-decoration: underline; } +.breadcrumb-item + .breadcrumb-item:hover::before { + text-decoration: none; } +.breadcrumb-item.active { + color: #6c757d; } + +.pagination { + display: flex; + padding-left: 0; + list-style: none; + border-radius: 0.25rem; } + +.page-link { + position: relative; + display: block; + padding: 0.5rem 0.75rem; + margin-left: -1px; + line-height: 1.25; + color: #3A9ABF; + background-color: #fff; + border: 1px solid #dee2e6; } + .page-link:hover { + z-index: 2; + color: #286b84; + text-decoration: none; + background-color: #e9ecef; + border-color: #dee2e6; } + .page-link:focus { + z-index: 2; + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(58, 154, 191, 0.25); } + +.page-item:first-child .page-link { + margin-left: 0; + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; } +.page-item:last-child .page-link { + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; } +.page-item.active .page-link { + z-index: 1; + color: #fff; + background-color: #3A9ABF; + border-color: #3A9ABF; } +.page-item.disabled .page-link { + color: #6c757d; + pointer-events: none; + cursor: auto; + background-color: #fff; + border-color: #dee2e6; } + +.pagination-lg .page-link { + padding: 0.75rem 1.5rem; + font-size: 1.25rem; + line-height: 1.5; } +.pagination-lg .page-item:first-child .page-link { + border-top-left-radius: 0.3rem; + border-bottom-left-radius: 0.3rem; } +.pagination-lg .page-item:last-child .page-link { + border-top-right-radius: 0.3rem; + border-bottom-right-radius: 0.3rem; } + +.pagination-sm .page-link { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; } +.pagination-sm .page-item:first-child .page-link { + border-top-left-radius: 0.2rem; + border-bottom-left-radius: 0.2rem; } +.pagination-sm .page-item:last-child .page-link { + border-top-right-radius: 0.2rem; + border-bottom-right-radius: 0.2rem; } + +.badge { + display: inline-block; + padding: 0.25em 0.4em; + font-size: 75%; + font-weight: 700; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 0.25rem; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } + @media (prefers-reduced-motion: reduce) { + .badge { + transition: none; } } + a.badge:hover, a.badge:focus { + text-decoration: none; } + .badge:empty { + display: none; } + +.btn .badge { + position: relative; + top: -1px; } + +.badge-pill { + padding-right: 0.6em; + padding-left: 0.6em; + border-radius: 10rem; } + +.badge-primary { + color: #fff; + background-color: #3A9ABF; } + a.badge-primary:hover, a.badge-primary:focus { + color: #fff; + background-color: #2e7a98; } + a.badge-primary:focus, a.badge-primary.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(58, 154, 191, 0.5); } + +.badge-secondary { + color: #fff; + background-color: #6C757D; } + a.badge-secondary:hover, a.badge-secondary:focus { + color: #fff; + background-color: #545b62; } + a.badge-secondary:focus, a.badge-secondary.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); } + +.badge-success { + color: #212529; + background-color: #75CC39; } + a.badge-success:hover, a.badge-success:focus { + color: #212529; + background-color: #5ea72b; } + a.badge-success:focus, a.badge-success.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(117, 204, 57, 0.5); } + +.badge-info { + color: #fff; + background-color: #17a2b8; } + a.badge-info:hover, a.badge-info:focus { + color: #fff; + background-color: #117a8b; } + a.badge-info:focus, a.badge-info.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); } + +.badge-warning { + color: #212529; + background-color: #FDC02E; } + a.badge-warning:hover, a.badge-warning:focus { + color: #212529; + background-color: #f6ae02; } + a.badge-warning:focus, a.badge-warning.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(253, 192, 46, 0.5); } + +.badge-danger { + color: #fff; + background-color: #D93749; } + a.badge-danger:hover, a.badge-danger:focus { + color: #fff; + background-color: #ba2334; } + a.badge-danger:focus, a.badge-danger.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(217, 55, 73, 0.5); } + +.badge-light { + color: #212529; + background-color: #f8f9fa; } + a.badge-light:hover, a.badge-light:focus { + color: #212529; + background-color: #dae0e5; } + a.badge-light:focus, a.badge-light.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); } + +.badge-dark { + color: #fff; + background-color: #343a40; } + a.badge-dark:hover, a.badge-dark:focus { + color: #fff; + background-color: #1d2124; } + a.badge-dark:focus, a.badge-dark.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); } + +.jumbotron { + padding: 2rem 1rem; + margin-bottom: 2rem; + background-color: #e9ecef; + border-radius: 0.3rem; } + @media (min-width: 576px) { + .jumbotron { + padding: 4rem 2rem; } } + +.jumbotron-fluid { + padding-right: 0; + padding-left: 0; + border-radius: 0; } + +.alert { + position: relative; + padding: 0.75rem 1.25rem; + margin-bottom: 1rem; + border: 1px solid transparent; + border-radius: 0.25rem; } + +.alert-heading { + color: inherit; } + +.alert-link { + font-weight: 700; } + +.alert-dismissible { + padding-right: 4rem; } + .alert-dismissible .close { + position: absolute; + top: 0; + right: 0; + padding: 0.75rem 1.25rem; + color: inherit; } + +.alert-primary { + color: #1e5063; + background-color: #d8ebf2; + border-color: #c8e3ed; } + .alert-primary hr { + border-top-color: #b5d9e7; } + .alert-primary .alert-link { + color: #12303c; } + +.alert-secondary { + color: #383d41; + background-color: #e2e3e5; + border-color: #d6d8db; } + .alert-secondary hr { + border-top-color: #c8cbcf; } + .alert-secondary .alert-link { + color: #202326; } + +.alert-success { + color: #3d6a1e; + background-color: #e3f5d7; + border-color: #d8f1c8; } + .alert-success hr { + border-top-color: #caecb4; } + .alert-success .alert-link { + color: #264213; } + +.alert-info { + color: #0c5460; + background-color: #d1ecf1; + border-color: #bee5eb; } + .alert-info hr { + border-top-color: #abdde5; } + .alert-info .alert-link { + color: #062c33; } + +.alert-warning { + color: #846418; + background-color: #fff2d5; + border-color: #feedc4; } + .alert-warning hr { + border-top-color: #fee5ab; } + .alert-warning .alert-link { + color: #594310; } + +.alert-danger { + color: #711d26; + background-color: #f7d7db; + border-color: #f4c7cc; } + .alert-danger hr { + border-top-color: #f0b2b9; } + .alert-danger .alert-link { + color: #481318; } + +.alert-light { + color: #818182; + background-color: #fefefe; + border-color: #fdfdfe; } + .alert-light hr { + border-top-color: #ececf6; } + .alert-light .alert-link { + color: #686868; } + +.alert-dark { + color: #1b1e21; + background-color: #d6d8d9; + border-color: #c6c8ca; } + .alert-dark hr { + border-top-color: #b9bbbe; } + .alert-dark .alert-link { + color: #040505; } + +@keyframes progress-bar-stripes { + from { + background-position: 1rem 0; } + to { + background-position: 0 0; } } +.progress { + display: flex; + height: 1rem; + overflow: hidden; + font-size: 0.75rem; + background-color: #e9ecef; + border-radius: 0.25rem; } + +.progress-bar { + display: flex; + flex-direction: column; + justify-content: center; + color: #fff; + text-align: center; + white-space: nowrap; + background-color: #3A9ABF; + transition: width 0.6s ease; } + @media (prefers-reduced-motion: reduce) { + .progress-bar { + transition: none; } } + +.progress-bar-striped { + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-size: 1rem 1rem; } + +.progress-bar-animated { + animation: progress-bar-stripes 1s linear infinite; } + @media (prefers-reduced-motion: reduce) { + .progress-bar-animated { + animation: none; } } + +.media { + display: flex; + align-items: flex-start; } + +.media-body { + flex: 1; } + +.list-group { + display: flex; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; } + +.list-group-item-action { + width: 100%; + color: #495057; + text-align: inherit; } + .list-group-item-action:hover, .list-group-item-action:focus { + z-index: 1; + color: #495057; + text-decoration: none; + background-color: #f8f9fa; } + .list-group-item-action:active { + color: #212529; + background-color: #e9ecef; } + +.list-group-item { + position: relative; + display: block; + padding: 0.25rem 0.5rem; + margin-bottom: -1px; + background-color: #fff; + border: 1px solid rgba(0, 0, 0, 0.125); } + .list-group-item:first-child { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; } + .list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; } + .list-group-item.disabled, .list-group-item:disabled { + color: #6c757d; + pointer-events: none; + background-color: #fff; } + .list-group-item.active { + z-index: 2; + color: #fff; + background-color: #3A9ABF; + border-color: #3A9ABF; } + +.list-group-horizontal { + flex-direction: row; } + .list-group-horizontal .list-group-item { + margin-right: -1px; + margin-bottom: 0; } + .list-group-horizontal .list-group-item:first-child { + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; + border-top-right-radius: 0; } + .list-group-horizontal .list-group-item:last-child { + margin-right: 0; + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0; } + +@media (min-width: 576px) { + .list-group-horizontal-sm { + flex-direction: row; } + .list-group-horizontal-sm .list-group-item { + margin-right: -1px; + margin-bottom: 0; } + .list-group-horizontal-sm .list-group-item:first-child { + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; + border-top-right-radius: 0; } + .list-group-horizontal-sm .list-group-item:last-child { + margin-right: 0; + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0; } } +@media (min-width: 768px) { + .list-group-horizontal-md { + flex-direction: row; } + .list-group-horizontal-md .list-group-item { + margin-right: -1px; + margin-bottom: 0; } + .list-group-horizontal-md .list-group-item:first-child { + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; + border-top-right-radius: 0; } + .list-group-horizontal-md .list-group-item:last-child { + margin-right: 0; + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0; } } +@media (min-width: 992px) { + .list-group-horizontal-lg { + flex-direction: row; } + .list-group-horizontal-lg .list-group-item { + margin-right: -1px; + margin-bottom: 0; } + .list-group-horizontal-lg .list-group-item:first-child { + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; + border-top-right-radius: 0; } + .list-group-horizontal-lg .list-group-item:last-child { + margin-right: 0; + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0; } } +@media (min-width: 1200px) { + .list-group-horizontal-xl { + flex-direction: row; } + .list-group-horizontal-xl .list-group-item { + margin-right: -1px; + margin-bottom: 0; } + .list-group-horizontal-xl .list-group-item:first-child { + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; + border-top-right-radius: 0; } + .list-group-horizontal-xl .list-group-item:last-child { + margin-right: 0; + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0; } } +.list-group-flush .list-group-item { + border-right: 0; + border-left: 0; + border-radius: 0; } + .list-group-flush .list-group-item:last-child { + margin-bottom: -1px; } +.list-group-flush:first-child .list-group-item:first-child { + border-top: 0; } +.list-group-flush:last-child .list-group-item:last-child { + margin-bottom: 0; + border-bottom: 0; } + +.list-group-item-primary { + color: #1e5063; + background-color: #c8e3ed; } + .list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus { + color: #1e5063; + background-color: #b5d9e7; } + .list-group-item-primary.list-group-item-action.active { + color: #fff; + background-color: #1e5063; + border-color: #1e5063; } + +.list-group-item-secondary { + color: #383d41; + background-color: #d6d8db; } + .list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus { + color: #383d41; + background-color: #c8cbcf; } + .list-group-item-secondary.list-group-item-action.active { + color: #fff; + background-color: #383d41; + border-color: #383d41; } + +.list-group-item-success { + color: #3d6a1e; + background-color: #d8f1c8; } + .list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus { + color: #3d6a1e; + background-color: #caecb4; } + .list-group-item-success.list-group-item-action.active { + color: #fff; + background-color: #3d6a1e; + border-color: #3d6a1e; } + +.list-group-item-info { + color: #0c5460; + background-color: #bee5eb; } + .list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus { + color: #0c5460; + background-color: #abdde5; } + .list-group-item-info.list-group-item-action.active { + color: #fff; + background-color: #0c5460; + border-color: #0c5460; } + +.list-group-item-warning { + color: #846418; + background-color: #feedc4; } + .list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus { + color: #846418; + background-color: #fee5ab; } + .list-group-item-warning.list-group-item-action.active { + color: #fff; + background-color: #846418; + border-color: #846418; } + +.list-group-item-danger { + color: #711d26; + background-color: #f4c7cc; } + .list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus { + color: #711d26; + background-color: #f0b2b9; } + .list-group-item-danger.list-group-item-action.active { + color: #fff; + background-color: #711d26; + border-color: #711d26; } + +.list-group-item-light { + color: #818182; + background-color: #fdfdfe; } + .list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus { + color: #818182; + background-color: #ececf6; } + .list-group-item-light.list-group-item-action.active { + color: #fff; + background-color: #818182; + border-color: #818182; } + +.list-group-item-dark { + color: #1b1e21; + background-color: #c6c8ca; } + .list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus { + color: #1b1e21; + background-color: #b9bbbe; } + .list-group-item-dark.list-group-item-action.active { + color: #fff; + background-color: #1b1e21; + border-color: #1b1e21; } + +.close { + float: right; + font-size: 1.5rem; + font-weight: 700; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + opacity: .5; } + .close:hover { + color: #000; + text-decoration: none; } + .close:not(:disabled):not(.disabled):hover, .close:not(:disabled):not(.disabled):focus { + opacity: .75; } + +button.close { + padding: 0; + background-color: transparent; + border: 0; + appearance: none; } + +a.close.disabled { + pointer-events: none; } + +.toast { + max-width: 350px; + overflow: hidden; + font-size: 0.875rem; + background-color: rgba(255, 255, 255, 0.85); + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.1); + box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.1); + backdrop-filter: blur(10px); + opacity: 0; + border-radius: 0.25rem; } + .toast:not(:last-child) { + margin-bottom: 0.75rem; } + .toast.showing { + opacity: 1; } + .toast.show { + display: block; + opacity: 1; } + .toast.hide { + display: none; } + +.toast-header { + display: flex; + align-items: center; + padding: 0.25rem 0.75rem; + color: #6c757d; + background-color: rgba(255, 255, 255, 0.85); + background-clip: padding-box; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); } + +.toast-body { + padding: 0.75rem; } + +.modal-open { + overflow: hidden; } + .modal-open .modal { + overflow-x: hidden; + overflow-y: auto; } + +.modal { + position: fixed; + top: 0; + left: 0; + z-index: 1050; + display: none; + width: 100%; + height: 100%; + overflow: hidden; + outline: 0; } + +.modal-dialog { + position: relative; + width: auto; + margin: 0.5rem; + pointer-events: none; } + .modal.fade .modal-dialog { + transition: transform 0.3s ease-out; + transform: translate(0, -50px); } + @media (prefers-reduced-motion: reduce) { + .modal.fade .modal-dialog { + transition: none; } } + .modal.show .modal-dialog { + transform: none; } + +.modal-dialog-scrollable { + display: flex; + max-height: calc(100% - 1rem); } + .modal-dialog-scrollable .modal-content { + max-height: calc(100vh - 1rem); + overflow: hidden; } + .modal-dialog-scrollable .modal-header, + .modal-dialog-scrollable .modal-footer { + flex-shrink: 0; } + .modal-dialog-scrollable .modal-body { + overflow-y: auto; } + +.modal-dialog-centered { + display: flex; + align-items: center; + min-height: calc(100% - 1rem); } + .modal-dialog-centered::before { + display: block; + height: calc(100vh - 1rem); + content: ""; } + .modal-dialog-centered.modal-dialog-scrollable { + flex-direction: column; + justify-content: center; + height: 100%; } + .modal-dialog-centered.modal-dialog-scrollable .modal-content { + max-height: none; } + .modal-dialog-centered.modal-dialog-scrollable::before { + content: none; } + +.modal-content { + position: relative; + display: flex; + flex-direction: column; + width: 100%; + pointer-events: auto; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 0.3rem; + outline: 0; } + +.modal-backdrop { + position: fixed; + top: 0; + left: 0; + z-index: 1040; + width: 100vw; + height: 100vh; + background-color: #000; } + .modal-backdrop.fade { + opacity: 0; } + .modal-backdrop.show { + opacity: 0.5; } + +.modal-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + padding: 1rem 1rem; + border-bottom: 1px solid #dee2e6; + border-top-left-radius: 0.3rem; + border-top-right-radius: 0.3rem; } + .modal-header .close { + padding: 1rem 1rem; + margin: -1rem -1rem -1rem auto; } + +.modal-title { + margin-bottom: 0; + line-height: 1.5; } + +.modal-body { + position: relative; + flex: 1 1 auto; + padding: 1rem; } + +.modal-footer { + display: flex; + align-items: center; + justify-content: flex-end; + padding: 1rem; + border-top: 1px solid #dee2e6; + border-bottom-right-radius: 0.3rem; + border-bottom-left-radius: 0.3rem; } + .modal-footer > :not(:first-child) { + margin-left: .25rem; } + .modal-footer > :not(:last-child) { + margin-right: .25rem; } + +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; } + +@media (min-width: 576px) { + .modal-dialog { + max-width: 500px; + margin: 1.75rem auto; } + + .modal-dialog-scrollable { + max-height: calc(100% - 3.5rem); } + .modal-dialog-scrollable .modal-content { + max-height: calc(100vh - 3.5rem); } + + .modal-dialog-centered { + min-height: calc(100% - 3.5rem); } + .modal-dialog-centered::before { + height: calc(100vh - 3.5rem); } + + .modal-sm { + max-width: 300px; } } +@media (min-width: 992px) { + .modal-lg, + .modal-xl { + max-width: 800px; } } +@media (min-width: 1200px) { + .modal-xl { + max-width: 1140px; } } +.tooltip { + position: absolute; + z-index: 1070; + display: block; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + white-space: normal; + line-break: auto; + font-size: 0.875rem; + word-wrap: break-word; + opacity: 0; } + .tooltip.show { + opacity: 0.9; } + .tooltip .arrow { + position: absolute; + display: block; + width: 0.8rem; + height: 0.4rem; } + .tooltip .arrow::before { + position: absolute; + content: ""; + border-color: transparent; + border-style: solid; } + +.bs-tooltip-top, .bs-tooltip-auto[x-placement^="top"] { + padding: 0.4rem 0; } + .bs-tooltip-top .arrow, .bs-tooltip-auto[x-placement^="top"] .arrow { + bottom: 0; } + .bs-tooltip-top .arrow::before, .bs-tooltip-auto[x-placement^="top"] .arrow::before { + top: 0; + border-width: 0.4rem 0.4rem 0; + border-top-color: #000; } + +.bs-tooltip-right, .bs-tooltip-auto[x-placement^="right"] { + padding: 0 0.4rem; } + .bs-tooltip-right .arrow, .bs-tooltip-auto[x-placement^="right"] .arrow { + left: 0; + width: 0.4rem; + height: 0.8rem; } + .bs-tooltip-right .arrow::before, .bs-tooltip-auto[x-placement^="right"] .arrow::before { + right: 0; + border-width: 0.4rem 0.4rem 0.4rem 0; + border-right-color: #000; } + +.bs-tooltip-bottom, .bs-tooltip-auto[x-placement^="bottom"] { + padding: 0.4rem 0; } + .bs-tooltip-bottom .arrow, .bs-tooltip-auto[x-placement^="bottom"] .arrow { + top: 0; } + .bs-tooltip-bottom .arrow::before, .bs-tooltip-auto[x-placement^="bottom"] .arrow::before { + bottom: 0; + border-width: 0 0.4rem 0.4rem; + border-bottom-color: #000; } + +.bs-tooltip-left, .bs-tooltip-auto[x-placement^="left"] { + padding: 0 0.4rem; } + .bs-tooltip-left .arrow, .bs-tooltip-auto[x-placement^="left"] .arrow { + right: 0; + width: 0.4rem; + height: 0.8rem; } + .bs-tooltip-left .arrow::before, .bs-tooltip-auto[x-placement^="left"] .arrow::before { + left: 0; + border-width: 0.4rem 0 0.4rem 0.4rem; + border-left-color: #000; } + +.tooltip-inner { + max-width: 200px; + padding: 0.25rem 0.5rem; + color: #fff; + text-align: center; + background-color: #000; + border-radius: 0.25rem; } + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: block; + max-width: 276px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + white-space: normal; + line-break: auto; + font-size: 0.875rem; + word-wrap: break-word; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 0.3rem; } + .popover .arrow { + position: absolute; + display: block; + width: 1rem; + height: 0.5rem; + margin: 0 0.3rem; } + .popover .arrow::before, .popover .arrow::after { + position: absolute; + display: block; + content: ""; + border-color: transparent; + border-style: solid; } + +.bs-popover-top, .bs-popover-auto[x-placement^="top"] { + margin-bottom: 0.5rem; } + .bs-popover-top > .arrow, .bs-popover-auto[x-placement^="top"] > .arrow { + bottom: calc((0.5rem + 1px) * -1); } + .bs-popover-top > .arrow::before, .bs-popover-auto[x-placement^="top"] > .arrow::before { + bottom: 0; + border-width: 0.5rem 0.5rem 0; + border-top-color: rgba(0, 0, 0, 0.25); } + .bs-popover-top > .arrow::after, .bs-popover-auto[x-placement^="top"] > .arrow::after { + bottom: 1px; + border-width: 0.5rem 0.5rem 0; + border-top-color: #fff; } + +.bs-popover-right, .bs-popover-auto[x-placement^="right"] { + margin-left: 0.5rem; } + .bs-popover-right > .arrow, .bs-popover-auto[x-placement^="right"] > .arrow { + left: calc((0.5rem + 1px) * -1); + width: 0.5rem; + height: 1rem; + margin: 0.3rem 0; } + .bs-popover-right > .arrow::before, .bs-popover-auto[x-placement^="right"] > .arrow::before { + left: 0; + border-width: 0.5rem 0.5rem 0.5rem 0; + border-right-color: rgba(0, 0, 0, 0.25); } + .bs-popover-right > .arrow::after, .bs-popover-auto[x-placement^="right"] > .arrow::after { + left: 1px; + border-width: 0.5rem 0.5rem 0.5rem 0; + border-right-color: #fff; } + +.bs-popover-bottom, .bs-popover-auto[x-placement^="bottom"] { + margin-top: 0.5rem; } + .bs-popover-bottom > .arrow, .bs-popover-auto[x-placement^="bottom"] > .arrow { + top: calc((0.5rem + 1px) * -1); } + .bs-popover-bottom > .arrow::before, .bs-popover-auto[x-placement^="bottom"] > .arrow::before { + top: 0; + border-width: 0 0.5rem 0.5rem 0.5rem; + border-bottom-color: rgba(0, 0, 0, 0.25); } + .bs-popover-bottom > .arrow::after, .bs-popover-auto[x-placement^="bottom"] > .arrow::after { + top: 1px; + border-width: 0 0.5rem 0.5rem 0.5rem; + border-bottom-color: #fff; } + .bs-popover-bottom .popover-header::before, .bs-popover-auto[x-placement^="bottom"] .popover-header::before { + position: absolute; + top: 0; + left: 50%; + display: block; + width: 1rem; + margin-left: -0.5rem; + content: ""; + border-bottom: 1px solid #f7f7f7; } + +.bs-popover-left, .bs-popover-auto[x-placement^="left"] { + margin-right: 0.5rem; } + .bs-popover-left > .arrow, .bs-popover-auto[x-placement^="left"] > .arrow { + right: calc((0.5rem + 1px) * -1); + width: 0.5rem; + height: 1rem; + margin: 0.3rem 0; } + .bs-popover-left > .arrow::before, .bs-popover-auto[x-placement^="left"] > .arrow::before { + right: 0; + border-width: 0.5rem 0 0.5rem 0.5rem; + border-left-color: rgba(0, 0, 0, 0.25); } + .bs-popover-left > .arrow::after, .bs-popover-auto[x-placement^="left"] > .arrow::after { + right: 1px; + border-width: 0.5rem 0 0.5rem 0.5rem; + border-left-color: #fff; } + +.popover-header { + padding: 0.5rem 0.75rem; + margin-bottom: 0; + font-size: 1rem; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-top-left-radius: calc(0.3rem - 1px); + border-top-right-radius: calc(0.3rem - 1px); } + .popover-header:empty { + display: none; } + +.popover-body { + padding: 0.5rem 0.75rem; + color: #212529; } + +.carousel { + position: relative; } + +.carousel.pointer-event { + touch-action: pan-y; } + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; } + .carousel-inner::after { + display: block; + clear: both; + content: ""; } + +.carousel-item { + position: relative; + display: none; + float: left; + width: 100%; + margin-right: -100%; + backface-visibility: hidden; + transition: transform 0.6s ease-in-out; } + @media (prefers-reduced-motion: reduce) { + .carousel-item { + transition: none; } } + +.carousel-item.active, +.carousel-item-next, +.carousel-item-prev { + display: block; } + +.carousel-item-next:not(.carousel-item-left), +.active.carousel-item-right { + transform: translateX(100%); } + +.carousel-item-prev:not(.carousel-item-right), +.active.carousel-item-left { + transform: translateX(-100%); } + +.carousel-fade .carousel-item { + opacity: 0; + transition-property: opacity; + transform: none; } +.carousel-fade .carousel-item.active, +.carousel-fade .carousel-item-next.carousel-item-left, +.carousel-fade .carousel-item-prev.carousel-item-right { + z-index: 1; + opacity: 1; } +.carousel-fade .active.carousel-item-left, +.carousel-fade .active.carousel-item-right { + z-index: 0; + opacity: 0; + transition: 0s 0.6s opacity; } + @media (prefers-reduced-motion: reduce) { + .carousel-fade .active.carousel-item-left, + .carousel-fade .active.carousel-item-right { + transition: none; } } + +.carousel-control-prev, +.carousel-control-next { + position: absolute; + top: 0; + bottom: 0; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + width: 15%; + color: #fff; + text-align: center; + opacity: 0.5; + transition: opacity 0.15s ease; } + @media (prefers-reduced-motion: reduce) { + .carousel-control-prev, + .carousel-control-next { + transition: none; } } + .carousel-control-prev:hover, .carousel-control-prev:focus, + .carousel-control-next:hover, + .carousel-control-next:focus { + color: #fff; + text-decoration: none; + outline: 0; + opacity: 0.9; } + +.carousel-control-prev { + left: 0; } + +.carousel-control-next { + right: 0; } + +.carousel-control-prev-icon, +.carousel-control-next-icon { + display: inline-block; + width: 20px; + height: 20px; + background: no-repeat 50% / 100% 100%; } + +.carousel-control-prev-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3e%3c/svg%3e"); } + +.carousel-control-next-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3e%3c/svg%3e"); } + +.carousel-indicators { + position: absolute; + right: 0; + bottom: 0; + left: 0; + z-index: 15; + display: flex; + justify-content: center; + padding-left: 0; + margin-right: 15%; + margin-left: 15%; + list-style: none; } + .carousel-indicators li { + box-sizing: content-box; + flex: 0 1 auto; + width: 30px; + height: 3px; + margin-right: 3px; + margin-left: 3px; + text-indent: -999px; + cursor: pointer; + background-color: #fff; + background-clip: padding-box; + border-top: 10px solid transparent; + border-bottom: 10px solid transparent; + opacity: .5; + transition: opacity 0.6s ease; } + @media (prefers-reduced-motion: reduce) { + .carousel-indicators li { + transition: none; } } + .carousel-indicators .active { + opacity: 1; } + +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; } + +@keyframes spinner-border { + to { + transform: rotate(360deg); } } +.spinner-border { + display: inline-block; + width: 2rem; + height: 2rem; + vertical-align: text-bottom; + border: 0.25em solid currentColor; + border-right-color: transparent; + border-radius: 50%; + animation: spinner-border .75s linear infinite; } + +.spinner-border-sm { + width: 1rem; + height: 1rem; + border-width: 0.2em; } + +@keyframes spinner-grow { + 0% { + transform: scale(0); } + 50% { + opacity: 1; } } +.spinner-grow { + display: inline-block; + width: 2rem; + height: 2rem; + vertical-align: text-bottom; + background-color: currentColor; + border-radius: 50%; + opacity: 0; + animation: spinner-grow .75s linear infinite; } + +.spinner-grow-sm { + width: 1rem; + height: 1rem; } + +.align-baseline { + vertical-align: baseline !important; } + +.align-top { + vertical-align: top !important; } + +.align-middle { + vertical-align: middle !important; } + +.align-bottom { + vertical-align: bottom !important; } + +.align-text-bottom { + vertical-align: text-bottom !important; } + +.align-text-top { + vertical-align: text-top !important; } + +.bg-primary { + background-color: #3A9ABF !important; } + +a.bg-primary:hover, a.bg-primary:focus, +button.bg-primary:hover, +button.bg-primary:focus { + background-color: #2e7a98 !important; } + +.bg-secondary { + background-color: #6C757D !important; } + +a.bg-secondary:hover, a.bg-secondary:focus, +button.bg-secondary:hover, +button.bg-secondary:focus { + background-color: #545b62 !important; } + +.bg-success { + background-color: #75CC39 !important; } + +a.bg-success:hover, a.bg-success:focus, +button.bg-success:hover, +button.bg-success:focus { + background-color: #5ea72b !important; } + +.bg-info { + background-color: #17a2b8 !important; } + +a.bg-info:hover, a.bg-info:focus, +button.bg-info:hover, +button.bg-info:focus { + background-color: #117a8b !important; } + +.bg-warning { + background-color: #FDC02E !important; } + +a.bg-warning:hover, a.bg-warning:focus, +button.bg-warning:hover, +button.bg-warning:focus { + background-color: #f6ae02 !important; } + +.bg-danger { + background-color: #D93749 !important; } + +a.bg-danger:hover, a.bg-danger:focus, +button.bg-danger:hover, +button.bg-danger:focus { + background-color: #ba2334 !important; } + +.bg-light { + background-color: #f8f9fa !important; } + +a.bg-light:hover, a.bg-light:focus, +button.bg-light:hover, +button.bg-light:focus { + background-color: #dae0e5 !important; } + +.bg-dark { + background-color: #343a40 !important; } + +a.bg-dark:hover, a.bg-dark:focus, +button.bg-dark:hover, +button.bg-dark:focus { + background-color: #1d2124 !important; } + +.bg-white { + background-color: #fff !important; } + +.bg-transparent { + background-color: transparent !important; } + +.border { + border: 1px solid #dee2e6 !important; } + +.border-top { + border-top: 1px solid #dee2e6 !important; } + +.border-right { + border-right: 1px solid #dee2e6 !important; } + +.border-bottom { + border-bottom: 1px solid #dee2e6 !important; } + +.border-left { + border-left: 1px solid #dee2e6 !important; } + +.border-0 { + border: 0 !important; } + +.border-top-0 { + border-top: 0 !important; } + +.border-right-0 { + border-right: 0 !important; } + +.border-bottom-0 { + border-bottom: 0 !important; } + +.border-left-0 { + border-left: 0 !important; } + +.border-primary { + border-color: #3A9ABF !important; } + +.border-secondary { + border-color: #6C757D !important; } + +.border-success { + border-color: #75CC39 !important; } + +.border-info { + border-color: #17a2b8 !important; } + +.border-warning { + border-color: #FDC02E !important; } + +.border-danger { + border-color: #D93749 !important; } + +.border-light { + border-color: #f8f9fa !important; } + +.border-dark { + border-color: #343a40 !important; } + +.border-white { + border-color: #fff !important; } + +.rounded-sm { + border-radius: 0.2rem !important; } + +.rounded { + border-radius: 0.25rem !important; } + +.rounded-top { + border-top-left-radius: 0.25rem !important; + border-top-right-radius: 0.25rem !important; } + +.rounded-right { + border-top-right-radius: 0.25rem !important; + border-bottom-right-radius: 0.25rem !important; } + +.rounded-bottom { + border-bottom-right-radius: 0.25rem !important; + border-bottom-left-radius: 0.25rem !important; } + +.rounded-left { + border-top-left-radius: 0.25rem !important; + border-bottom-left-radius: 0.25rem !important; } + +.rounded-lg { + border-radius: 0.3rem !important; } + +.rounded-circle { + border-radius: 50% !important; } + +.rounded-pill { + border-radius: 50rem !important; } + +.rounded-0 { + border-radius: 0 !important; } + +.clearfix::after { + display: block; + clear: both; + content: ""; } + +.d-none { + display: none !important; } + +.d-inline { + display: inline !important; } + +.d-inline-block { + display: inline-block !important; } + +.d-block { + display: block !important; } + +.d-table { + display: table !important; } + +.d-table-row { + display: table-row !important; } + +.d-table-cell { + display: table-cell !important; } + +.d-flex { + display: flex !important; } + +.d-inline-flex { + display: inline-flex !important; } + +@media (min-width: 576px) { + .d-sm-none { + display: none !important; } + + .d-sm-inline { + display: inline !important; } + + .d-sm-inline-block { + display: inline-block !important; } + + .d-sm-block { + display: block !important; } + + .d-sm-table { + display: table !important; } + + .d-sm-table-row { + display: table-row !important; } + + .d-sm-table-cell { + display: table-cell !important; } + + .d-sm-flex { + display: flex !important; } + + .d-sm-inline-flex { + display: inline-flex !important; } } +@media (min-width: 768px) { + .d-md-none { + display: none !important; } + + .d-md-inline { + display: inline !important; } + + .d-md-inline-block { + display: inline-block !important; } + + .d-md-block { + display: block !important; } + + .d-md-table { + display: table !important; } + + .d-md-table-row { + display: table-row !important; } + + .d-md-table-cell { + display: table-cell !important; } + + .d-md-flex { + display: flex !important; } + + .d-md-inline-flex { + display: inline-flex !important; } } +@media (min-width: 992px) { + .d-lg-none { + display: none !important; } + + .d-lg-inline { + display: inline !important; } + + .d-lg-inline-block { + display: inline-block !important; } + + .d-lg-block { + display: block !important; } + + .d-lg-table { + display: table !important; } + + .d-lg-table-row { + display: table-row !important; } + + .d-lg-table-cell { + display: table-cell !important; } + + .d-lg-flex { + display: flex !important; } + + .d-lg-inline-flex { + display: inline-flex !important; } } +@media (min-width: 1200px) { + .d-xl-none { + display: none !important; } + + .d-xl-inline { + display: inline !important; } + + .d-xl-inline-block { + display: inline-block !important; } + + .d-xl-block { + display: block !important; } + + .d-xl-table { + display: table !important; } + + .d-xl-table-row { + display: table-row !important; } + + .d-xl-table-cell { + display: table-cell !important; } + + .d-xl-flex { + display: flex !important; } + + .d-xl-inline-flex { + display: inline-flex !important; } } +@media print { + .d-print-none { + display: none !important; } + + .d-print-inline { + display: inline !important; } + + .d-print-inline-block { + display: inline-block !important; } + + .d-print-block { + display: block !important; } + + .d-print-table { + display: table !important; } + + .d-print-table-row { + display: table-row !important; } + + .d-print-table-cell { + display: table-cell !important; } + + .d-print-flex { + display: flex !important; } + + .d-print-inline-flex { + display: inline-flex !important; } } +.embed-responsive { + position: relative; + display: block; + width: 100%; + padding: 0; + overflow: hidden; } + .embed-responsive::before { + display: block; + content: ""; } + .embed-responsive .embed-responsive-item, + .embed-responsive iframe, + .embed-responsive embed, + .embed-responsive object, + .embed-responsive video { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; } + +.embed-responsive-21by9::before { + padding-top: 42.8571428571%; } + +.embed-responsive-16by9::before { + padding-top: 56.25%; } + +.embed-responsive-4by3::before { + padding-top: 75%; } + +.embed-responsive-1by1::before { + padding-top: 100%; } + +.flex-row { + flex-direction: row !important; } + +.flex-column { + flex-direction: column !important; } + +.flex-row-reverse { + flex-direction: row-reverse !important; } + +.flex-column-reverse { + flex-direction: column-reverse !important; } + +.flex-wrap { + flex-wrap: wrap !important; } + +.flex-nowrap { + flex-wrap: nowrap !important; } + +.flex-wrap-reverse { + flex-wrap: wrap-reverse !important; } + +.flex-fill { + flex: 1 1 auto !important; } + +.flex-grow-0 { + flex-grow: 0 !important; } + +.flex-grow-1 { + flex-grow: 1 !important; } + +.flex-shrink-0 { + flex-shrink: 0 !important; } + +.flex-shrink-1 { + flex-shrink: 1 !important; } + +.justify-content-start { + justify-content: flex-start !important; } + +.justify-content-end { + justify-content: flex-end !important; } + +.justify-content-center { + justify-content: center !important; } + +.justify-content-between { + justify-content: space-between !important; } + +.justify-content-around { + justify-content: space-around !important; } + +.align-items-start { + align-items: flex-start !important; } + +.align-items-end { + align-items: flex-end !important; } + +.align-items-center { + align-items: center !important; } + +.align-items-baseline { + align-items: baseline !important; } + +.align-items-stretch { + align-items: stretch !important; } + +.align-content-start { + align-content: flex-start !important; } + +.align-content-end { + align-content: flex-end !important; } + +.align-content-center { + align-content: center !important; } + +.align-content-between { + align-content: space-between !important; } + +.align-content-around { + align-content: space-around !important; } + +.align-content-stretch { + align-content: stretch !important; } + +.align-self-auto { + align-self: auto !important; } + +.align-self-start { + align-self: flex-start !important; } + +.align-self-end { + align-self: flex-end !important; } + +.align-self-center { + align-self: center !important; } + +.align-self-baseline { + align-self: baseline !important; } + +.align-self-stretch { + align-self: stretch !important; } + +@media (min-width: 576px) { + .flex-sm-row { + flex-direction: row !important; } + + .flex-sm-column { + flex-direction: column !important; } + + .flex-sm-row-reverse { + flex-direction: row-reverse !important; } + + .flex-sm-column-reverse { + flex-direction: column-reverse !important; } + + .flex-sm-wrap { + flex-wrap: wrap !important; } + + .flex-sm-nowrap { + flex-wrap: nowrap !important; } + + .flex-sm-wrap-reverse { + flex-wrap: wrap-reverse !important; } + + .flex-sm-fill { + flex: 1 1 auto !important; } + + .flex-sm-grow-0 { + flex-grow: 0 !important; } + + .flex-sm-grow-1 { + flex-grow: 1 !important; } + + .flex-sm-shrink-0 { + flex-shrink: 0 !important; } + + .flex-sm-shrink-1 { + flex-shrink: 1 !important; } + + .justify-content-sm-start { + justify-content: flex-start !important; } + + .justify-content-sm-end { + justify-content: flex-end !important; } + + .justify-content-sm-center { + justify-content: center !important; } + + .justify-content-sm-between { + justify-content: space-between !important; } + + .justify-content-sm-around { + justify-content: space-around !important; } + + .align-items-sm-start { + align-items: flex-start !important; } + + .align-items-sm-end { + align-items: flex-end !important; } + + .align-items-sm-center { + align-items: center !important; } + + .align-items-sm-baseline { + align-items: baseline !important; } + + .align-items-sm-stretch { + align-items: stretch !important; } + + .align-content-sm-start { + align-content: flex-start !important; } + + .align-content-sm-end { + align-content: flex-end !important; } + + .align-content-sm-center { + align-content: center !important; } + + .align-content-sm-between { + align-content: space-between !important; } + + .align-content-sm-around { + align-content: space-around !important; } + + .align-content-sm-stretch { + align-content: stretch !important; } + + .align-self-sm-auto { + align-self: auto !important; } + + .align-self-sm-start { + align-self: flex-start !important; } + + .align-self-sm-end { + align-self: flex-end !important; } + + .align-self-sm-center { + align-self: center !important; } + + .align-self-sm-baseline { + align-self: baseline !important; } + + .align-self-sm-stretch { + align-self: stretch !important; } } +@media (min-width: 768px) { + .flex-md-row { + flex-direction: row !important; } + + .flex-md-column { + flex-direction: column !important; } + + .flex-md-row-reverse { + flex-direction: row-reverse !important; } + + .flex-md-column-reverse { + flex-direction: column-reverse !important; } + + .flex-md-wrap { + flex-wrap: wrap !important; } + + .flex-md-nowrap { + flex-wrap: nowrap !important; } + + .flex-md-wrap-reverse { + flex-wrap: wrap-reverse !important; } + + .flex-md-fill { + flex: 1 1 auto !important; } + + .flex-md-grow-0 { + flex-grow: 0 !important; } + + .flex-md-grow-1 { + flex-grow: 1 !important; } + + .flex-md-shrink-0 { + flex-shrink: 0 !important; } + + .flex-md-shrink-1 { + flex-shrink: 1 !important; } + + .justify-content-md-start { + justify-content: flex-start !important; } + + .justify-content-md-end { + justify-content: flex-end !important; } + + .justify-content-md-center { + justify-content: center !important; } + + .justify-content-md-between { + justify-content: space-between !important; } + + .justify-content-md-around { + justify-content: space-around !important; } + + .align-items-md-start { + align-items: flex-start !important; } + + .align-items-md-end { + align-items: flex-end !important; } + + .align-items-md-center { + align-items: center !important; } + + .align-items-md-baseline { + align-items: baseline !important; } + + .align-items-md-stretch { + align-items: stretch !important; } + + .align-content-md-start { + align-content: flex-start !important; } + + .align-content-md-end { + align-content: flex-end !important; } + + .align-content-md-center { + align-content: center !important; } + + .align-content-md-between { + align-content: space-between !important; } + + .align-content-md-around { + align-content: space-around !important; } + + .align-content-md-stretch { + align-content: stretch !important; } + + .align-self-md-auto { + align-self: auto !important; } + + .align-self-md-start { + align-self: flex-start !important; } + + .align-self-md-end { + align-self: flex-end !important; } + + .align-self-md-center { + align-self: center !important; } + + .align-self-md-baseline { + align-self: baseline !important; } + + .align-self-md-stretch { + align-self: stretch !important; } } +@media (min-width: 992px) { + .flex-lg-row { + flex-direction: row !important; } + + .flex-lg-column { + flex-direction: column !important; } + + .flex-lg-row-reverse { + flex-direction: row-reverse !important; } + + .flex-lg-column-reverse { + flex-direction: column-reverse !important; } + + .flex-lg-wrap { + flex-wrap: wrap !important; } + + .flex-lg-nowrap { + flex-wrap: nowrap !important; } + + .flex-lg-wrap-reverse { + flex-wrap: wrap-reverse !important; } + + .flex-lg-fill { + flex: 1 1 auto !important; } + + .flex-lg-grow-0 { + flex-grow: 0 !important; } + + .flex-lg-grow-1 { + flex-grow: 1 !important; } + + .flex-lg-shrink-0 { + flex-shrink: 0 !important; } + + .flex-lg-shrink-1 { + flex-shrink: 1 !important; } + + .justify-content-lg-start { + justify-content: flex-start !important; } + + .justify-content-lg-end { + justify-content: flex-end !important; } + + .justify-content-lg-center { + justify-content: center !important; } + + .justify-content-lg-between { + justify-content: space-between !important; } + + .justify-content-lg-around { + justify-content: space-around !important; } + + .align-items-lg-start { + align-items: flex-start !important; } + + .align-items-lg-end { + align-items: flex-end !important; } + + .align-items-lg-center { + align-items: center !important; } + + .align-items-lg-baseline { + align-items: baseline !important; } + + .align-items-lg-stretch { + align-items: stretch !important; } + + .align-content-lg-start { + align-content: flex-start !important; } + + .align-content-lg-end { + align-content: flex-end !important; } + + .align-content-lg-center { + align-content: center !important; } + + .align-content-lg-between { + align-content: space-between !important; } + + .align-content-lg-around { + align-content: space-around !important; } + + .align-content-lg-stretch { + align-content: stretch !important; } + + .align-self-lg-auto { + align-self: auto !important; } + + .align-self-lg-start { + align-self: flex-start !important; } + + .align-self-lg-end { + align-self: flex-end !important; } + + .align-self-lg-center { + align-self: center !important; } + + .align-self-lg-baseline { + align-self: baseline !important; } + + .align-self-lg-stretch { + align-self: stretch !important; } } +@media (min-width: 1200px) { + .flex-xl-row { + flex-direction: row !important; } + + .flex-xl-column { + flex-direction: column !important; } + + .flex-xl-row-reverse { + flex-direction: row-reverse !important; } + + .flex-xl-column-reverse { + flex-direction: column-reverse !important; } + + .flex-xl-wrap { + flex-wrap: wrap !important; } + + .flex-xl-nowrap { + flex-wrap: nowrap !important; } + + .flex-xl-wrap-reverse { + flex-wrap: wrap-reverse !important; } + + .flex-xl-fill { + flex: 1 1 auto !important; } + + .flex-xl-grow-0 { + flex-grow: 0 !important; } + + .flex-xl-grow-1 { + flex-grow: 1 !important; } + + .flex-xl-shrink-0 { + flex-shrink: 0 !important; } + + .flex-xl-shrink-1 { + flex-shrink: 1 !important; } + + .justify-content-xl-start { + justify-content: flex-start !important; } + + .justify-content-xl-end { + justify-content: flex-end !important; } + + .justify-content-xl-center { + justify-content: center !important; } + + .justify-content-xl-between { + justify-content: space-between !important; } + + .justify-content-xl-around { + justify-content: space-around !important; } + + .align-items-xl-start { + align-items: flex-start !important; } + + .align-items-xl-end { + align-items: flex-end !important; } + + .align-items-xl-center { + align-items: center !important; } + + .align-items-xl-baseline { + align-items: baseline !important; } + + .align-items-xl-stretch { + align-items: stretch !important; } + + .align-content-xl-start { + align-content: flex-start !important; } + + .align-content-xl-end { + align-content: flex-end !important; } + + .align-content-xl-center { + align-content: center !important; } + + .align-content-xl-between { + align-content: space-between !important; } + + .align-content-xl-around { + align-content: space-around !important; } + + .align-content-xl-stretch { + align-content: stretch !important; } + + .align-self-xl-auto { + align-self: auto !important; } + + .align-self-xl-start { + align-self: flex-start !important; } + + .align-self-xl-end { + align-self: flex-end !important; } + + .align-self-xl-center { + align-self: center !important; } + + .align-self-xl-baseline { + align-self: baseline !important; } + + .align-self-xl-stretch { + align-self: stretch !important; } } +.float-left { + float: left !important; } + +.float-right { + float: right !important; } + +.float-none { + float: none !important; } + +@media (min-width: 576px) { + .float-sm-left { + float: left !important; } + + .float-sm-right { + float: right !important; } + + .float-sm-none { + float: none !important; } } +@media (min-width: 768px) { + .float-md-left { + float: left !important; } + + .float-md-right { + float: right !important; } + + .float-md-none { + float: none !important; } } +@media (min-width: 992px) { + .float-lg-left { + float: left !important; } + + .float-lg-right { + float: right !important; } + + .float-lg-none { + float: none !important; } } +@media (min-width: 1200px) { + .float-xl-left { + float: left !important; } + + .float-xl-right { + float: right !important; } + + .float-xl-none { + float: none !important; } } +.overflow-auto { + overflow: auto !important; } + +.overflow-hidden { + overflow: hidden !important; } + +.position-static { + position: static !important; } + +.position-relative { + position: relative !important; } + +.position-absolute { + position: absolute !important; } + +.position-fixed { + position: fixed !important; } + +.position-sticky { + position: sticky !important; } + +.fixed-top { + position: fixed; + top: 0; + right: 0; + left: 0; + z-index: 1030; } + +.fixed-bottom { + position: fixed; + right: 0; + bottom: 0; + left: 0; + z-index: 1030; } + +@supports (position: sticky) { + .sticky-top { + position: sticky; + top: 0; + z-index: 1020; } } + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; } + +.sr-only-focusable:active, .sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + overflow: visible; + clip: auto; + white-space: normal; } + +.shadow-sm { + box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important; } + +.shadow { + box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important; } + +.shadow-lg { + box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important; } + +.shadow-none { + box-shadow: none !important; } + +.w-25 { + width: 25% !important; } + +.w-50 { + width: 50% !important; } + +.w-75 { + width: 75% !important; } + +.w-100 { + width: 100% !important; } + +.w-auto { + width: auto !important; } + +.h-25 { + height: 25% !important; } + +.h-50 { + height: 50% !important; } + +.h-75 { + height: 75% !important; } + +.h-100 { + height: 100% !important; } + +.h-auto { + height: auto !important; } + +.mw-100 { + max-width: 100% !important; } + +.mh-100 { + max-height: 100% !important; } + +.min-vw-100 { + min-width: 100vw !important; } + +.min-vh-100 { + min-height: 100vh !important; } + +.vw-100 { + width: 100vw !important; } + +.vh-100 { + height: 100vh !important; } + +.stretched-link::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1; + pointer-events: auto; + content: ""; + background-color: rgba(0, 0, 0, 0); } + +.m-0 { + margin: 0 !important; } + +.mt-0, +.my-0 { + margin-top: 0 !important; } + +.mr-0, +.mx-0 { + margin-right: 0 !important; } + +.mb-0, +.my-0 { + margin-bottom: 0 !important; } + +.ml-0, +.mx-0 { + margin-left: 0 !important; } + +.m-1 { + margin: 0.25rem !important; } + +.mt-1, +.my-1 { + margin-top: 0.25rem !important; } + +.mr-1, +.mx-1 { + margin-right: 0.25rem !important; } + +.mb-1, +.my-1 { + margin-bottom: 0.25rem !important; } + +.ml-1, +.mx-1 { + margin-left: 0.25rem !important; } + +.m-2 { + margin: 0.5rem !important; } + +.mt-2, +.my-2 { + margin-top: 0.5rem !important; } + +.mr-2, +.mx-2 { + margin-right: 0.5rem !important; } + +.mb-2, +.my-2 { + margin-bottom: 0.5rem !important; } + +.ml-2, +.mx-2 { + margin-left: 0.5rem !important; } + +.m-3 { + margin: 1rem !important; } + +.mt-3, +.my-3 { + margin-top: 1rem !important; } + +.mr-3, +.mx-3 { + margin-right: 1rem !important; } + +.mb-3, +.my-3 { + margin-bottom: 1rem !important; } + +.ml-3, +.mx-3 { + margin-left: 1rem !important; } + +.m-4 { + margin: 1.5rem !important; } + +.mt-4, +.my-4 { + margin-top: 1.5rem !important; } + +.mr-4, +.mx-4 { + margin-right: 1.5rem !important; } + +.mb-4, +.my-4 { + margin-bottom: 1.5rem !important; } + +.ml-4, +.mx-4 { + margin-left: 1.5rem !important; } + +.m-5 { + margin: 3rem !important; } + +.mt-5, +.my-5 { + margin-top: 3rem !important; } + +.mr-5, +.mx-5 { + margin-right: 3rem !important; } + +.mb-5, +.my-5 { + margin-bottom: 3rem !important; } + +.ml-5, +.mx-5 { + margin-left: 3rem !important; } + +.p-0 { + padding: 0 !important; } + +.pt-0, +.py-0 { + padding-top: 0 !important; } + +.pr-0, +.px-0 { + padding-right: 0 !important; } + +.pb-0, +.py-0 { + padding-bottom: 0 !important; } + +.pl-0, +.px-0 { + padding-left: 0 !important; } + +.p-1 { + padding: 0.25rem !important; } + +.pt-1, +.py-1 { + padding-top: 0.25rem !important; } + +.pr-1, +.px-1 { + padding-right: 0.25rem !important; } + +.pb-1, +.py-1 { + padding-bottom: 0.25rem !important; } + +.pl-1, +.px-1 { + padding-left: 0.25rem !important; } + +.p-2 { + padding: 0.5rem !important; } + +.pt-2, +.py-2 { + padding-top: 0.5rem !important; } + +.pr-2, +.px-2 { + padding-right: 0.5rem !important; } + +.pb-2, +.py-2 { + padding-bottom: 0.5rem !important; } + +.pl-2, +.px-2 { + padding-left: 0.5rem !important; } + +.p-3 { + padding: 1rem !important; } + +.pt-3, +.py-3 { + padding-top: 1rem !important; } + +.pr-3, +.px-3 { + padding-right: 1rem !important; } + +.pb-3, +.py-3 { + padding-bottom: 1rem !important; } + +.pl-3, +.px-3 { + padding-left: 1rem !important; } + +.p-4 { + padding: 1.5rem !important; } + +.pt-4, +.py-4 { + padding-top: 1.5rem !important; } + +.pr-4, +.px-4 { + padding-right: 1.5rem !important; } + +.pb-4, +.py-4 { + padding-bottom: 1.5rem !important; } + +.pl-4, +.px-4 { + padding-left: 1.5rem !important; } + +.p-5 { + padding: 3rem !important; } + +.pt-5, +.py-5 { + padding-top: 3rem !important; } + +.pr-5, +.px-5 { + padding-right: 3rem !important; } + +.pb-5, +.py-5 { + padding-bottom: 3rem !important; } + +.pl-5, +.px-5 { + padding-left: 3rem !important; } + +.m-n1 { + margin: -0.25rem !important; } + +.mt-n1, +.my-n1 { + margin-top: -0.25rem !important; } + +.mr-n1, +.mx-n1 { + margin-right: -0.25rem !important; } + +.mb-n1, +.my-n1 { + margin-bottom: -0.25rem !important; } + +.ml-n1, +.mx-n1 { + margin-left: -0.25rem !important; } + +.m-n2 { + margin: -0.5rem !important; } + +.mt-n2, +.my-n2 { + margin-top: -0.5rem !important; } + +.mr-n2, +.mx-n2 { + margin-right: -0.5rem !important; } + +.mb-n2, +.my-n2 { + margin-bottom: -0.5rem !important; } + +.ml-n2, +.mx-n2 { + margin-left: -0.5rem !important; } + +.m-n3 { + margin: -1rem !important; } + +.mt-n3, +.my-n3 { + margin-top: -1rem !important; } + +.mr-n3, +.mx-n3 { + margin-right: -1rem !important; } + +.mb-n3, +.my-n3 { + margin-bottom: -1rem !important; } + +.ml-n3, +.mx-n3 { + margin-left: -1rem !important; } + +.m-n4 { + margin: -1.5rem !important; } + +.mt-n4, +.my-n4 { + margin-top: -1.5rem !important; } + +.mr-n4, +.mx-n4 { + margin-right: -1.5rem !important; } + +.mb-n4, +.my-n4 { + margin-bottom: -1.5rem !important; } + +.ml-n4, +.mx-n4 { + margin-left: -1.5rem !important; } + +.m-n5 { + margin: -3rem !important; } + +.mt-n5, +.my-n5 { + margin-top: -3rem !important; } + +.mr-n5, +.mx-n5 { + margin-right: -3rem !important; } + +.mb-n5, +.my-n5 { + margin-bottom: -3rem !important; } + +.ml-n5, +.mx-n5 { + margin-left: -3rem !important; } + +.m-auto { + margin: auto !important; } + +.mt-auto, +.my-auto { + margin-top: auto !important; } + +.mr-auto, +.mx-auto { + margin-right: auto !important; } + +.mb-auto, +.my-auto { + margin-bottom: auto !important; } + +.ml-auto, +.mx-auto { + margin-left: auto !important; } + +@media (min-width: 576px) { + .m-sm-0 { + margin: 0 !important; } + + .mt-sm-0, + .my-sm-0 { + margin-top: 0 !important; } + + .mr-sm-0, + .mx-sm-0 { + margin-right: 0 !important; } + + .mb-sm-0, + .my-sm-0 { + margin-bottom: 0 !important; } + + .ml-sm-0, + .mx-sm-0 { + margin-left: 0 !important; } + + .m-sm-1 { + margin: 0.25rem !important; } + + .mt-sm-1, + .my-sm-1 { + margin-top: 0.25rem !important; } + + .mr-sm-1, + .mx-sm-1 { + margin-right: 0.25rem !important; } + + .mb-sm-1, + .my-sm-1 { + margin-bottom: 0.25rem !important; } + + .ml-sm-1, + .mx-sm-1 { + margin-left: 0.25rem !important; } + + .m-sm-2 { + margin: 0.5rem !important; } + + .mt-sm-2, + .my-sm-2 { + margin-top: 0.5rem !important; } + + .mr-sm-2, + .mx-sm-2 { + margin-right: 0.5rem !important; } + + .mb-sm-2, + .my-sm-2 { + margin-bottom: 0.5rem !important; } + + .ml-sm-2, + .mx-sm-2 { + margin-left: 0.5rem !important; } + + .m-sm-3 { + margin: 1rem !important; } + + .mt-sm-3, + .my-sm-3 { + margin-top: 1rem !important; } + + .mr-sm-3, + .mx-sm-3 { + margin-right: 1rem !important; } + + .mb-sm-3, + .my-sm-3 { + margin-bottom: 1rem !important; } + + .ml-sm-3, + .mx-sm-3 { + margin-left: 1rem !important; } + + .m-sm-4 { + margin: 1.5rem !important; } + + .mt-sm-4, + .my-sm-4 { + margin-top: 1.5rem !important; } + + .mr-sm-4, + .mx-sm-4 { + margin-right: 1.5rem !important; } + + .mb-sm-4, + .my-sm-4 { + margin-bottom: 1.5rem !important; } + + .ml-sm-4, + .mx-sm-4 { + margin-left: 1.5rem !important; } + + .m-sm-5 { + margin: 3rem !important; } + + .mt-sm-5, + .my-sm-5 { + margin-top: 3rem !important; } + + .mr-sm-5, + .mx-sm-5 { + margin-right: 3rem !important; } + + .mb-sm-5, + .my-sm-5 { + margin-bottom: 3rem !important; } + + .ml-sm-5, + .mx-sm-5 { + margin-left: 3rem !important; } + + .p-sm-0 { + padding: 0 !important; } + + .pt-sm-0, + .py-sm-0 { + padding-top: 0 !important; } + + .pr-sm-0, + .px-sm-0 { + padding-right: 0 !important; } + + .pb-sm-0, + .py-sm-0 { + padding-bottom: 0 !important; } + + .pl-sm-0, + .px-sm-0 { + padding-left: 0 !important; } + + .p-sm-1 { + padding: 0.25rem !important; } + + .pt-sm-1, + .py-sm-1 { + padding-top: 0.25rem !important; } + + .pr-sm-1, + .px-sm-1 { + padding-right: 0.25rem !important; } + + .pb-sm-1, + .py-sm-1 { + padding-bottom: 0.25rem !important; } + + .pl-sm-1, + .px-sm-1 { + padding-left: 0.25rem !important; } + + .p-sm-2 { + padding: 0.5rem !important; } + + .pt-sm-2, + .py-sm-2 { + padding-top: 0.5rem !important; } + + .pr-sm-2, + .px-sm-2 { + padding-right: 0.5rem !important; } + + .pb-sm-2, + .py-sm-2 { + padding-bottom: 0.5rem !important; } + + .pl-sm-2, + .px-sm-2 { + padding-left: 0.5rem !important; } + + .p-sm-3 { + padding: 1rem !important; } + + .pt-sm-3, + .py-sm-3 { + padding-top: 1rem !important; } + + .pr-sm-3, + .px-sm-3 { + padding-right: 1rem !important; } + + .pb-sm-3, + .py-sm-3 { + padding-bottom: 1rem !important; } + + .pl-sm-3, + .px-sm-3 { + padding-left: 1rem !important; } + + .p-sm-4 { + padding: 1.5rem !important; } + + .pt-sm-4, + .py-sm-4 { + padding-top: 1.5rem !important; } + + .pr-sm-4, + .px-sm-4 { + padding-right: 1.5rem !important; } + + .pb-sm-4, + .py-sm-4 { + padding-bottom: 1.5rem !important; } + + .pl-sm-4, + .px-sm-4 { + padding-left: 1.5rem !important; } + + .p-sm-5 { + padding: 3rem !important; } + + .pt-sm-5, + .py-sm-5 { + padding-top: 3rem !important; } + + .pr-sm-5, + .px-sm-5 { + padding-right: 3rem !important; } + + .pb-sm-5, + .py-sm-5 { + padding-bottom: 3rem !important; } + + .pl-sm-5, + .px-sm-5 { + padding-left: 3rem !important; } + + .m-sm-n1 { + margin: -0.25rem !important; } + + .mt-sm-n1, + .my-sm-n1 { + margin-top: -0.25rem !important; } + + .mr-sm-n1, + .mx-sm-n1 { + margin-right: -0.25rem !important; } + + .mb-sm-n1, + .my-sm-n1 { + margin-bottom: -0.25rem !important; } + + .ml-sm-n1, + .mx-sm-n1 { + margin-left: -0.25rem !important; } + + .m-sm-n2 { + margin: -0.5rem !important; } + + .mt-sm-n2, + .my-sm-n2 { + margin-top: -0.5rem !important; } + + .mr-sm-n2, + .mx-sm-n2 { + margin-right: -0.5rem !important; } + + .mb-sm-n2, + .my-sm-n2 { + margin-bottom: -0.5rem !important; } + + .ml-sm-n2, + .mx-sm-n2 { + margin-left: -0.5rem !important; } + + .m-sm-n3 { + margin: -1rem !important; } + + .mt-sm-n3, + .my-sm-n3 { + margin-top: -1rem !important; } + + .mr-sm-n3, + .mx-sm-n3 { + margin-right: -1rem !important; } + + .mb-sm-n3, + .my-sm-n3 { + margin-bottom: -1rem !important; } + + .ml-sm-n3, + .mx-sm-n3 { + margin-left: -1rem !important; } + + .m-sm-n4 { + margin: -1.5rem !important; } + + .mt-sm-n4, + .my-sm-n4 { + margin-top: -1.5rem !important; } + + .mr-sm-n4, + .mx-sm-n4 { + margin-right: -1.5rem !important; } + + .mb-sm-n4, + .my-sm-n4 { + margin-bottom: -1.5rem !important; } + + .ml-sm-n4, + .mx-sm-n4 { + margin-left: -1.5rem !important; } + + .m-sm-n5 { + margin: -3rem !important; } + + .mt-sm-n5, + .my-sm-n5 { + margin-top: -3rem !important; } + + .mr-sm-n5, + .mx-sm-n5 { + margin-right: -3rem !important; } + + .mb-sm-n5, + .my-sm-n5 { + margin-bottom: -3rem !important; } + + .ml-sm-n5, + .mx-sm-n5 { + margin-left: -3rem !important; } + + .m-sm-auto { + margin: auto !important; } + + .mt-sm-auto, + .my-sm-auto { + margin-top: auto !important; } + + .mr-sm-auto, + .mx-sm-auto { + margin-right: auto !important; } + + .mb-sm-auto, + .my-sm-auto { + margin-bottom: auto !important; } + + .ml-sm-auto, + .mx-sm-auto { + margin-left: auto !important; } } +@media (min-width: 768px) { + .m-md-0 { + margin: 0 !important; } + + .mt-md-0, + .my-md-0 { + margin-top: 0 !important; } + + .mr-md-0, + .mx-md-0 { + margin-right: 0 !important; } + + .mb-md-0, + .my-md-0 { + margin-bottom: 0 !important; } + + .ml-md-0, + .mx-md-0 { + margin-left: 0 !important; } + + .m-md-1 { + margin: 0.25rem !important; } + + .mt-md-1, + .my-md-1 { + margin-top: 0.25rem !important; } + + .mr-md-1, + .mx-md-1 { + margin-right: 0.25rem !important; } + + .mb-md-1, + .my-md-1 { + margin-bottom: 0.25rem !important; } + + .ml-md-1, + .mx-md-1 { + margin-left: 0.25rem !important; } + + .m-md-2 { + margin: 0.5rem !important; } + + .mt-md-2, + .my-md-2 { + margin-top: 0.5rem !important; } + + .mr-md-2, + .mx-md-2 { + margin-right: 0.5rem !important; } + + .mb-md-2, + .my-md-2 { + margin-bottom: 0.5rem !important; } + + .ml-md-2, + .mx-md-2 { + margin-left: 0.5rem !important; } + + .m-md-3 { + margin: 1rem !important; } + + .mt-md-3, + .my-md-3 { + margin-top: 1rem !important; } + + .mr-md-3, + .mx-md-3 { + margin-right: 1rem !important; } + + .mb-md-3, + .my-md-3 { + margin-bottom: 1rem !important; } + + .ml-md-3, + .mx-md-3 { + margin-left: 1rem !important; } + + .m-md-4 { + margin: 1.5rem !important; } + + .mt-md-4, + .my-md-4 { + margin-top: 1.5rem !important; } + + .mr-md-4, + .mx-md-4 { + margin-right: 1.5rem !important; } + + .mb-md-4, + .my-md-4 { + margin-bottom: 1.5rem !important; } + + .ml-md-4, + .mx-md-4 { + margin-left: 1.5rem !important; } + + .m-md-5 { + margin: 3rem !important; } + + .mt-md-5, + .my-md-5 { + margin-top: 3rem !important; } + + .mr-md-5, + .mx-md-5 { + margin-right: 3rem !important; } + + .mb-md-5, + .my-md-5 { + margin-bottom: 3rem !important; } + + .ml-md-5, + .mx-md-5 { + margin-left: 3rem !important; } + + .p-md-0 { + padding: 0 !important; } + + .pt-md-0, + .py-md-0 { + padding-top: 0 !important; } + + .pr-md-0, + .px-md-0 { + padding-right: 0 !important; } + + .pb-md-0, + .py-md-0 { + padding-bottom: 0 !important; } + + .pl-md-0, + .px-md-0 { + padding-left: 0 !important; } + + .p-md-1 { + padding: 0.25rem !important; } + + .pt-md-1, + .py-md-1 { + padding-top: 0.25rem !important; } + + .pr-md-1, + .px-md-1 { + padding-right: 0.25rem !important; } + + .pb-md-1, + .py-md-1 { + padding-bottom: 0.25rem !important; } + + .pl-md-1, + .px-md-1 { + padding-left: 0.25rem !important; } + + .p-md-2 { + padding: 0.5rem !important; } + + .pt-md-2, + .py-md-2 { + padding-top: 0.5rem !important; } + + .pr-md-2, + .px-md-2 { + padding-right: 0.5rem !important; } + + .pb-md-2, + .py-md-2 { + padding-bottom: 0.5rem !important; } + + .pl-md-2, + .px-md-2 { + padding-left: 0.5rem !important; } + + .p-md-3 { + padding: 1rem !important; } + + .pt-md-3, + .py-md-3 { + padding-top: 1rem !important; } + + .pr-md-3, + .px-md-3 { + padding-right: 1rem !important; } + + .pb-md-3, + .py-md-3 { + padding-bottom: 1rem !important; } + + .pl-md-3, + .px-md-3 { + padding-left: 1rem !important; } + + .p-md-4 { + padding: 1.5rem !important; } + + .pt-md-4, + .py-md-4 { + padding-top: 1.5rem !important; } + + .pr-md-4, + .px-md-4 { + padding-right: 1.5rem !important; } + + .pb-md-4, + .py-md-4 { + padding-bottom: 1.5rem !important; } + + .pl-md-4, + .px-md-4 { + padding-left: 1.5rem !important; } + + .p-md-5 { + padding: 3rem !important; } + + .pt-md-5, + .py-md-5 { + padding-top: 3rem !important; } + + .pr-md-5, + .px-md-5 { + padding-right: 3rem !important; } + + .pb-md-5, + .py-md-5 { + padding-bottom: 3rem !important; } + + .pl-md-5, + .px-md-5 { + padding-left: 3rem !important; } + + .m-md-n1 { + margin: -0.25rem !important; } + + .mt-md-n1, + .my-md-n1 { + margin-top: -0.25rem !important; } + + .mr-md-n1, + .mx-md-n1 { + margin-right: -0.25rem !important; } + + .mb-md-n1, + .my-md-n1 { + margin-bottom: -0.25rem !important; } + + .ml-md-n1, + .mx-md-n1 { + margin-left: -0.25rem !important; } + + .m-md-n2 { + margin: -0.5rem !important; } + + .mt-md-n2, + .my-md-n2 { + margin-top: -0.5rem !important; } + + .mr-md-n2, + .mx-md-n2 { + margin-right: -0.5rem !important; } + + .mb-md-n2, + .my-md-n2 { + margin-bottom: -0.5rem !important; } + + .ml-md-n2, + .mx-md-n2 { + margin-left: -0.5rem !important; } + + .m-md-n3 { + margin: -1rem !important; } + + .mt-md-n3, + .my-md-n3 { + margin-top: -1rem !important; } + + .mr-md-n3, + .mx-md-n3 { + margin-right: -1rem !important; } + + .mb-md-n3, + .my-md-n3 { + margin-bottom: -1rem !important; } + + .ml-md-n3, + .mx-md-n3 { + margin-left: -1rem !important; } + + .m-md-n4 { + margin: -1.5rem !important; } + + .mt-md-n4, + .my-md-n4 { + margin-top: -1.5rem !important; } + + .mr-md-n4, + .mx-md-n4 { + margin-right: -1.5rem !important; } + + .mb-md-n4, + .my-md-n4 { + margin-bottom: -1.5rem !important; } + + .ml-md-n4, + .mx-md-n4 { + margin-left: -1.5rem !important; } + + .m-md-n5 { + margin: -3rem !important; } + + .mt-md-n5, + .my-md-n5 { + margin-top: -3rem !important; } + + .mr-md-n5, + .mx-md-n5 { + margin-right: -3rem !important; } + + .mb-md-n5, + .my-md-n5 { + margin-bottom: -3rem !important; } + + .ml-md-n5, + .mx-md-n5 { + margin-left: -3rem !important; } + + .m-md-auto { + margin: auto !important; } + + .mt-md-auto, + .my-md-auto { + margin-top: auto !important; } + + .mr-md-auto, + .mx-md-auto { + margin-right: auto !important; } + + .mb-md-auto, + .my-md-auto { + margin-bottom: auto !important; } + + .ml-md-auto, + .mx-md-auto { + margin-left: auto !important; } } +@media (min-width: 992px) { + .m-lg-0 { + margin: 0 !important; } + + .mt-lg-0, + .my-lg-0 { + margin-top: 0 !important; } + + .mr-lg-0, + .mx-lg-0 { + margin-right: 0 !important; } + + .mb-lg-0, + .my-lg-0 { + margin-bottom: 0 !important; } + + .ml-lg-0, + .mx-lg-0 { + margin-left: 0 !important; } + + .m-lg-1 { + margin: 0.25rem !important; } + + .mt-lg-1, + .my-lg-1 { + margin-top: 0.25rem !important; } + + .mr-lg-1, + .mx-lg-1 { + margin-right: 0.25rem !important; } + + .mb-lg-1, + .my-lg-1 { + margin-bottom: 0.25rem !important; } + + .ml-lg-1, + .mx-lg-1 { + margin-left: 0.25rem !important; } + + .m-lg-2 { + margin: 0.5rem !important; } + + .mt-lg-2, + .my-lg-2 { + margin-top: 0.5rem !important; } + + .mr-lg-2, + .mx-lg-2 { + margin-right: 0.5rem !important; } + + .mb-lg-2, + .my-lg-2 { + margin-bottom: 0.5rem !important; } + + .ml-lg-2, + .mx-lg-2 { + margin-left: 0.5rem !important; } + + .m-lg-3 { + margin: 1rem !important; } + + .mt-lg-3, + .my-lg-3 { + margin-top: 1rem !important; } + + .mr-lg-3, + .mx-lg-3 { + margin-right: 1rem !important; } + + .mb-lg-3, + .my-lg-3 { + margin-bottom: 1rem !important; } + + .ml-lg-3, + .mx-lg-3 { + margin-left: 1rem !important; } + + .m-lg-4 { + margin: 1.5rem !important; } + + .mt-lg-4, + .my-lg-4 { + margin-top: 1.5rem !important; } + + .mr-lg-4, + .mx-lg-4 { + margin-right: 1.5rem !important; } + + .mb-lg-4, + .my-lg-4 { + margin-bottom: 1.5rem !important; } + + .ml-lg-4, + .mx-lg-4 { + margin-left: 1.5rem !important; } + + .m-lg-5 { + margin: 3rem !important; } + + .mt-lg-5, + .my-lg-5 { + margin-top: 3rem !important; } + + .mr-lg-5, + .mx-lg-5 { + margin-right: 3rem !important; } + + .mb-lg-5, + .my-lg-5 { + margin-bottom: 3rem !important; } + + .ml-lg-5, + .mx-lg-5 { + margin-left: 3rem !important; } + + .p-lg-0 { + padding: 0 !important; } + + .pt-lg-0, + .py-lg-0 { + padding-top: 0 !important; } + + .pr-lg-0, + .px-lg-0 { + padding-right: 0 !important; } + + .pb-lg-0, + .py-lg-0 { + padding-bottom: 0 !important; } + + .pl-lg-0, + .px-lg-0 { + padding-left: 0 !important; } + + .p-lg-1 { + padding: 0.25rem !important; } + + .pt-lg-1, + .py-lg-1 { + padding-top: 0.25rem !important; } + + .pr-lg-1, + .px-lg-1 { + padding-right: 0.25rem !important; } + + .pb-lg-1, + .py-lg-1 { + padding-bottom: 0.25rem !important; } + + .pl-lg-1, + .px-lg-1 { + padding-left: 0.25rem !important; } + + .p-lg-2 { + padding: 0.5rem !important; } + + .pt-lg-2, + .py-lg-2 { + padding-top: 0.5rem !important; } + + .pr-lg-2, + .px-lg-2 { + padding-right: 0.5rem !important; } + + .pb-lg-2, + .py-lg-2 { + padding-bottom: 0.5rem !important; } + + .pl-lg-2, + .px-lg-2 { + padding-left: 0.5rem !important; } + + .p-lg-3 { + padding: 1rem !important; } + + .pt-lg-3, + .py-lg-3 { + padding-top: 1rem !important; } + + .pr-lg-3, + .px-lg-3 { + padding-right: 1rem !important; } + + .pb-lg-3, + .py-lg-3 { + padding-bottom: 1rem !important; } + + .pl-lg-3, + .px-lg-3 { + padding-left: 1rem !important; } + + .p-lg-4 { + padding: 1.5rem !important; } + + .pt-lg-4, + .py-lg-4 { + padding-top: 1.5rem !important; } + + .pr-lg-4, + .px-lg-4 { + padding-right: 1.5rem !important; } + + .pb-lg-4, + .py-lg-4 { + padding-bottom: 1.5rem !important; } + + .pl-lg-4, + .px-lg-4 { + padding-left: 1.5rem !important; } + + .p-lg-5 { + padding: 3rem !important; } + + .pt-lg-5, + .py-lg-5 { + padding-top: 3rem !important; } + + .pr-lg-5, + .px-lg-5 { + padding-right: 3rem !important; } + + .pb-lg-5, + .py-lg-5 { + padding-bottom: 3rem !important; } + + .pl-lg-5, + .px-lg-5 { + padding-left: 3rem !important; } + + .m-lg-n1 { + margin: -0.25rem !important; } + + .mt-lg-n1, + .my-lg-n1 { + margin-top: -0.25rem !important; } + + .mr-lg-n1, + .mx-lg-n1 { + margin-right: -0.25rem !important; } + + .mb-lg-n1, + .my-lg-n1 { + margin-bottom: -0.25rem !important; } + + .ml-lg-n1, + .mx-lg-n1 { + margin-left: -0.25rem !important; } + + .m-lg-n2 { + margin: -0.5rem !important; } + + .mt-lg-n2, + .my-lg-n2 { + margin-top: -0.5rem !important; } + + .mr-lg-n2, + .mx-lg-n2 { + margin-right: -0.5rem !important; } + + .mb-lg-n2, + .my-lg-n2 { + margin-bottom: -0.5rem !important; } + + .ml-lg-n2, + .mx-lg-n2 { + margin-left: -0.5rem !important; } + + .m-lg-n3 { + margin: -1rem !important; } + + .mt-lg-n3, + .my-lg-n3 { + margin-top: -1rem !important; } + + .mr-lg-n3, + .mx-lg-n3 { + margin-right: -1rem !important; } + + .mb-lg-n3, + .my-lg-n3 { + margin-bottom: -1rem !important; } + + .ml-lg-n3, + .mx-lg-n3 { + margin-left: -1rem !important; } + + .m-lg-n4 { + margin: -1.5rem !important; } + + .mt-lg-n4, + .my-lg-n4 { + margin-top: -1.5rem !important; } + + .mr-lg-n4, + .mx-lg-n4 { + margin-right: -1.5rem !important; } + + .mb-lg-n4, + .my-lg-n4 { + margin-bottom: -1.5rem !important; } + + .ml-lg-n4, + .mx-lg-n4 { + margin-left: -1.5rem !important; } + + .m-lg-n5 { + margin: -3rem !important; } + + .mt-lg-n5, + .my-lg-n5 { + margin-top: -3rem !important; } + + .mr-lg-n5, + .mx-lg-n5 { + margin-right: -3rem !important; } + + .mb-lg-n5, + .my-lg-n5 { + margin-bottom: -3rem !important; } + + .ml-lg-n5, + .mx-lg-n5 { + margin-left: -3rem !important; } + + .m-lg-auto { + margin: auto !important; } + + .mt-lg-auto, + .my-lg-auto { + margin-top: auto !important; } + + .mr-lg-auto, + .mx-lg-auto { + margin-right: auto !important; } + + .mb-lg-auto, + .my-lg-auto { + margin-bottom: auto !important; } + + .ml-lg-auto, + .mx-lg-auto { + margin-left: auto !important; } } +@media (min-width: 1200px) { + .m-xl-0 { + margin: 0 !important; } + + .mt-xl-0, + .my-xl-0 { + margin-top: 0 !important; } + + .mr-xl-0, + .mx-xl-0 { + margin-right: 0 !important; } + + .mb-xl-0, + .my-xl-0 { + margin-bottom: 0 !important; } + + .ml-xl-0, + .mx-xl-0 { + margin-left: 0 !important; } + + .m-xl-1 { + margin: 0.25rem !important; } + + .mt-xl-1, + .my-xl-1 { + margin-top: 0.25rem !important; } + + .mr-xl-1, + .mx-xl-1 { + margin-right: 0.25rem !important; } + + .mb-xl-1, + .my-xl-1 { + margin-bottom: 0.25rem !important; } + + .ml-xl-1, + .mx-xl-1 { + margin-left: 0.25rem !important; } + + .m-xl-2 { + margin: 0.5rem !important; } + + .mt-xl-2, + .my-xl-2 { + margin-top: 0.5rem !important; } + + .mr-xl-2, + .mx-xl-2 { + margin-right: 0.5rem !important; } + + .mb-xl-2, + .my-xl-2 { + margin-bottom: 0.5rem !important; } + + .ml-xl-2, + .mx-xl-2 { + margin-left: 0.5rem !important; } + + .m-xl-3 { + margin: 1rem !important; } + + .mt-xl-3, + .my-xl-3 { + margin-top: 1rem !important; } + + .mr-xl-3, + .mx-xl-3 { + margin-right: 1rem !important; } + + .mb-xl-3, + .my-xl-3 { + margin-bottom: 1rem !important; } + + .ml-xl-3, + .mx-xl-3 { + margin-left: 1rem !important; } + + .m-xl-4 { + margin: 1.5rem !important; } + + .mt-xl-4, + .my-xl-4 { + margin-top: 1.5rem !important; } + + .mr-xl-4, + .mx-xl-4 { + margin-right: 1.5rem !important; } + + .mb-xl-4, + .my-xl-4 { + margin-bottom: 1.5rem !important; } + + .ml-xl-4, + .mx-xl-4 { + margin-left: 1.5rem !important; } + + .m-xl-5 { + margin: 3rem !important; } + + .mt-xl-5, + .my-xl-5 { + margin-top: 3rem !important; } + + .mr-xl-5, + .mx-xl-5 { + margin-right: 3rem !important; } + + .mb-xl-5, + .my-xl-5 { + margin-bottom: 3rem !important; } + + .ml-xl-5, + .mx-xl-5 { + margin-left: 3rem !important; } + + .p-xl-0 { + padding: 0 !important; } + + .pt-xl-0, + .py-xl-0 { + padding-top: 0 !important; } + + .pr-xl-0, + .px-xl-0 { + padding-right: 0 !important; } + + .pb-xl-0, + .py-xl-0 { + padding-bottom: 0 !important; } + + .pl-xl-0, + .px-xl-0 { + padding-left: 0 !important; } + + .p-xl-1 { + padding: 0.25rem !important; } + + .pt-xl-1, + .py-xl-1 { + padding-top: 0.25rem !important; } + + .pr-xl-1, + .px-xl-1 { + padding-right: 0.25rem !important; } + + .pb-xl-1, + .py-xl-1 { + padding-bottom: 0.25rem !important; } + + .pl-xl-1, + .px-xl-1 { + padding-left: 0.25rem !important; } + + .p-xl-2 { + padding: 0.5rem !important; } + + .pt-xl-2, + .py-xl-2 { + padding-top: 0.5rem !important; } + + .pr-xl-2, + .px-xl-2 { + padding-right: 0.5rem !important; } + + .pb-xl-2, + .py-xl-2 { + padding-bottom: 0.5rem !important; } + + .pl-xl-2, + .px-xl-2 { + padding-left: 0.5rem !important; } + + .p-xl-3 { + padding: 1rem !important; } + + .pt-xl-3, + .py-xl-3 { + padding-top: 1rem !important; } + + .pr-xl-3, + .px-xl-3 { + padding-right: 1rem !important; } + + .pb-xl-3, + .py-xl-3 { + padding-bottom: 1rem !important; } + + .pl-xl-3, + .px-xl-3 { + padding-left: 1rem !important; } + + .p-xl-4 { + padding: 1.5rem !important; } + + .pt-xl-4, + .py-xl-4 { + padding-top: 1.5rem !important; } + + .pr-xl-4, + .px-xl-4 { + padding-right: 1.5rem !important; } + + .pb-xl-4, + .py-xl-4 { + padding-bottom: 1.5rem !important; } + + .pl-xl-4, + .px-xl-4 { + padding-left: 1.5rem !important; } + + .p-xl-5 { + padding: 3rem !important; } + + .pt-xl-5, + .py-xl-5 { + padding-top: 3rem !important; } + + .pr-xl-5, + .px-xl-5 { + padding-right: 3rem !important; } + + .pb-xl-5, + .py-xl-5 { + padding-bottom: 3rem !important; } + + .pl-xl-5, + .px-xl-5 { + padding-left: 3rem !important; } + + .m-xl-n1 { + margin: -0.25rem !important; } + + .mt-xl-n1, + .my-xl-n1 { + margin-top: -0.25rem !important; } + + .mr-xl-n1, + .mx-xl-n1 { + margin-right: -0.25rem !important; } + + .mb-xl-n1, + .my-xl-n1 { + margin-bottom: -0.25rem !important; } + + .ml-xl-n1, + .mx-xl-n1 { + margin-left: -0.25rem !important; } + + .m-xl-n2 { + margin: -0.5rem !important; } + + .mt-xl-n2, + .my-xl-n2 { + margin-top: -0.5rem !important; } + + .mr-xl-n2, + .mx-xl-n2 { + margin-right: -0.5rem !important; } + + .mb-xl-n2, + .my-xl-n2 { + margin-bottom: -0.5rem !important; } + + .ml-xl-n2, + .mx-xl-n2 { + margin-left: -0.5rem !important; } + + .m-xl-n3 { + margin: -1rem !important; } + + .mt-xl-n3, + .my-xl-n3 { + margin-top: -1rem !important; } + + .mr-xl-n3, + .mx-xl-n3 { + margin-right: -1rem !important; } + + .mb-xl-n3, + .my-xl-n3 { + margin-bottom: -1rem !important; } + + .ml-xl-n3, + .mx-xl-n3 { + margin-left: -1rem !important; } + + .m-xl-n4 { + margin: -1.5rem !important; } + + .mt-xl-n4, + .my-xl-n4 { + margin-top: -1.5rem !important; } + + .mr-xl-n4, + .mx-xl-n4 { + margin-right: -1.5rem !important; } + + .mb-xl-n4, + .my-xl-n4 { + margin-bottom: -1.5rem !important; } + + .ml-xl-n4, + .mx-xl-n4 { + margin-left: -1.5rem !important; } + + .m-xl-n5 { + margin: -3rem !important; } + + .mt-xl-n5, + .my-xl-n5 { + margin-top: -3rem !important; } + + .mr-xl-n5, + .mx-xl-n5 { + margin-right: -3rem !important; } + + .mb-xl-n5, + .my-xl-n5 { + margin-bottom: -3rem !important; } + + .ml-xl-n5, + .mx-xl-n5 { + margin-left: -3rem !important; } + + .m-xl-auto { + margin: auto !important; } + + .mt-xl-auto, + .my-xl-auto { + margin-top: auto !important; } + + .mr-xl-auto, + .mx-xl-auto { + margin-right: auto !important; } + + .mb-xl-auto, + .my-xl-auto { + margin-bottom: auto !important; } + + .ml-xl-auto, + .mx-xl-auto { + margin-left: auto !important; } } +.text-monospace { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important; } + +.text-justify { + text-align: justify !important; } + +.text-wrap { + white-space: normal !important; } + +.text-nowrap { + white-space: nowrap !important; } + +.text-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } + +.text-left { + text-align: left !important; } + +.text-right { + text-align: right !important; } + +.text-center { + text-align: center !important; } + +@media (min-width: 576px) { + .text-sm-left { + text-align: left !important; } + + .text-sm-right { + text-align: right !important; } + + .text-sm-center { + text-align: center !important; } } +@media (min-width: 768px) { + .text-md-left { + text-align: left !important; } + + .text-md-right { + text-align: right !important; } + + .text-md-center { + text-align: center !important; } } +@media (min-width: 992px) { + .text-lg-left { + text-align: left !important; } + + .text-lg-right { + text-align: right !important; } + + .text-lg-center { + text-align: center !important; } } +@media (min-width: 1200px) { + .text-xl-left { + text-align: left !important; } + + .text-xl-right { + text-align: right !important; } + + .text-xl-center { + text-align: center !important; } } +.text-lowercase { + text-transform: lowercase !important; } + +.text-uppercase { + text-transform: uppercase !important; } + +.text-capitalize { + text-transform: capitalize !important; } + +.font-weight-light { + font-weight: 300 !important; } + +.font-weight-lighter { + font-weight: lighter !important; } + +.font-weight-normal { + font-weight: 400 !important; } + +.font-weight-bold { + font-weight: 700 !important; } + +.font-weight-bolder { + font-weight: bolder !important; } + +.font-italic { + font-style: italic !important; } + +.text-white { + color: #fff !important; } + +.text-primary { + color: #3A9ABF !important; } + +a.text-primary:hover, a.text-primary:focus { + color: #286b84 !important; } + +.text-secondary { + color: #6C757D !important; } + +a.text-secondary:hover, a.text-secondary:focus { + color: #494f54 !important; } + +.text-success { + color: #75CC39 !important; } + +a.text-success:hover, a.text-success:focus { + color: #529326 !important; } + +.text-info { + color: #17a2b8 !important; } + +a.text-info:hover, a.text-info:focus { + color: #0f6674 !important; } + +.text-warning { + color: #FDC02E !important; } + +a.text-warning:hover, a.text-warning:focus { + color: #dc9c02 !important; } + +.text-danger { + color: #D93749 !important; } + +a.text-danger:hover, a.text-danger:focus { + color: #a41f2e !important; } + +.text-light { + color: #f8f9fa !important; } + +a.text-light:hover, a.text-light:focus { + color: #cbd3da !important; } + +.text-dark { + color: #343a40 !important; } + +a.text-dark:hover, a.text-dark:focus { + color: #121416 !important; } + +.text-body { + color: #212529 !important; } + +.text-muted { + color: #6c757d !important; } + +.text-black-50 { + color: rgba(0, 0, 0, 0.5) !important; } + +.text-white-50 { + color: rgba(255, 255, 255, 0.5) !important; } + +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; } + +.text-decoration-none { + text-decoration: none !important; } + +.text-break { + word-break: break-word !important; + overflow-wrap: break-word !important; } + +.text-reset { + color: inherit !important; } + +.visible { + visibility: visible !important; } + +.invisible { + visibility: hidden !important; } + +@media print { + *, + *::before, + *::after { + text-shadow: none !important; + box-shadow: none !important; } + + a:not(.btn) { + text-decoration: underline; } + + abbr[title]::after { + content: " (" attr(title) ")"; } + + pre { + white-space: pre-wrap !important; } + + pre, + blockquote { + border: 1px solid #adb5bd; + page-break-inside: avoid; } + + thead { + display: table-header-group; } + + tr, + img { + page-break-inside: avoid; } + + p, + h2, + h3 { + orphans: 3; + widows: 3; } + + h2, + h3 { + page-break-after: avoid; } + + @page { + size: a3; } + body { + min-width: 992px !important; } + + .container { + min-width: 992px !important; } + + .navbar { + display: none; } + + .badge { + border: 1px solid #000; } + + .table { + border-collapse: collapse !important; } + .table td, + .table th { + background-color: #fff !important; } + + .table-bordered th, + .table-bordered td { + border: 1px solid #dee2e6 !important; } + + .table-dark { + color: inherit; } + .table-dark th, + .table-dark td, + .table-dark thead th, + .table-dark tbody + tbody { + border-color: #dee2e6; } + + .table .thead-dark th { + color: inherit; + border-color: #dee2e6; } } +/* Custom Styles */ +.list-group-item { + border: none; + padding: 0 0 0 .5rem; } + +.card-header, .card-body { + padding: 0.25rem; } + +.accordion > .card .card-header { + margin-bottom: 0; } + +.list-group-item:first-child { + border-radius: 0; } + +.table th, .table td { + padding: 0.25rem; } + +code { + color: black !important; } + +/*# sourceMappingURL=main.css.map */ From 7e95cc34804eb153f28347cda515e0a80917bdb0 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 30 Sep 2019 10:15:35 -0500 Subject: [PATCH 010/102] COMMANDBOX-1053 #resolve Copy the link to this issue Update ColdBox module templates for 5.0 standards --- .../templates/modules/ModuleConfig.cfc | 4 --- .../templates/modules/ModuleConfigScript.cfc | 4 --- .../templates/modules/config/Router.cfc | 9 ++++++ .../templates/orm/EntityScript.txt | 28 +++++++++++++++---- 4 files changed, 31 insertions(+), 14 deletions(-) create mode 100644 src/cfml/system/modules_app/coldbox-commands/templates/modules/config/Router.cfc diff --git a/src/cfml/system/modules_app/coldbox-commands/templates/modules/ModuleConfig.cfc b/src/cfml/system/modules_app/coldbox-commands/templates/modules/ModuleConfig.cfc index 228c3d643..20de6ea18 100644 --- a/src/cfml/system/modules_app/coldbox-commands/templates/modules/ModuleConfig.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/templates/modules/ModuleConfig.cfc @@ -88,10 +88,6 @@ Optional Methods // SES Routes routes = [ - // Module Entry Point - { pattern="/", handler="home", action="index" }, - // Convention Route - { pattern="/:handler/:action?" } ]; // SES Resources diff --git a/src/cfml/system/modules_app/coldbox-commands/templates/modules/ModuleConfigScript.cfc b/src/cfml/system/modules_app/coldbox-commands/templates/modules/ModuleConfigScript.cfc index f14cda4d4..22e704119 100644 --- a/src/cfml/system/modules_app/coldbox-commands/templates/modules/ModuleConfigScript.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/templates/modules/ModuleConfigScript.cfc @@ -84,10 +84,6 @@ component { // SES Routes routes = [ - // Module Entry Point - { pattern="/", handler="home", action="index" }, - // Convention Route - { pattern="/:handler/:action?" } ]; // SES Resources diff --git a/src/cfml/system/modules_app/coldbox-commands/templates/modules/config/Router.cfc b/src/cfml/system/modules_app/coldbox-commands/templates/modules/config/Router.cfc new file mode 100644 index 000000000..84de33828 --- /dev/null +++ b/src/cfml/system/modules_app/coldbox-commands/templates/modules/config/Router.cfc @@ -0,0 +1,9 @@ +component{ + + function configure(){ + + route( "/", "home.index" ); + + } + +} \ No newline at end of file diff --git a/src/cfml/system/modules_app/coldbox-commands/templates/orm/EntityScript.txt b/src/cfml/system/modules_app/coldbox-commands/templates/orm/EntityScript.txt index 67fa791b4..a5b4deece 100644 --- a/src/cfml/system/modules_app/coldbox-commands/templates/orm/EntityScript.txt +++ b/src/cfml/system/modules_app/coldbox-commands/templates/orm/EntityScript.txt @@ -1,20 +1,36 @@ /** -* A cool |entityName| entity -*/ + * A cool |entityName| entity + */ component persistent="true" table="|table|"|activeEntity|{ // Primary Key property name="|primaryKey|" fieldtype="id" column="|primaryKeyColumn|" generator="|generator|" setter="false"; - + // Properties |properties| - + // Validation this.constraints = { // Example: age = { required=true, min="18", type="numeric" } }; - - // Constructor + + // Mementifier + this.memento = { + // An array of the properties/relationships to include by default + defaultIncludes = [ "*" ], + // An array of properties/relationships to exclude by default + defaultExcludes = [], + // An array of properties/relationships to NEVER include + neverInclude = [], + // A struct of defaults for properties/relationships if they are null + defaults = {}, + // A struct of mapping functions for properties/relationships that can transform them + mappers = {} + }; + + /** + * Constructor + */ function init(){ |activeEntityInit| return this; From 0293ec7776b1f87cd32d9d0e461a3ea6a986bd4a Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 16 Oct 2019 12:06:48 +0200 Subject: [PATCH 011/102] COMMANDBOX-1054 --- src/cfml/system/services/CommandService.cfc | 78 ++++++++++----------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/src/cfml/system/services/CommandService.cfc b/src/cfml/system/services/CommandService.cfc index 49ad4c335..7e91f111a 100644 --- a/src/cfml/system/services/CommandService.cfc +++ b/src/cfml/system/services/CommandService.cfc @@ -90,7 +90,7 @@ component accessors="true" singleton { listInfo = 'query', sort = 'type desc, name asc' ); - + for( var dir in varDirs ){ // For CFC files, process them as a command if( dir.type == 'File' && listLast( dir.name, '.' ) == 'cfc' ){ @@ -131,7 +131,7 @@ component accessors="true" singleton { consoleLogger.warn( "Removing extra text [box ] from start of command. You don't need that here." ); arguments.line = right( arguments.line, len( arguments.line ) - 4 ); } - + // Resolve the command they are wanting to run var commandChain = resolveCommand( line ); @@ -230,7 +230,7 @@ component accessors="true" singleton { // Parameters need to be ALL positional or ALL named if( arrayLen( parameterInfo.positionalParameters ) && structCount( parameterInfo.namedParameters ) ){ shell.setExitCode( 1 ); - var detail = "You specified named parameters: #structKeyList(parameterInfo.namedParameters)# but you did not specify a name for: #parameterInfo.positionalParameters[1]# #chr(10)##chr(9)#" & line; + var detail = "You specified named parameters: #structKeyList(parameterInfo.namedParameters)# but you did not specify a name for: #parameterInfo.positionalParameters[1]# #chr(10)##chr(9)#" & line; throw( message='Please don''t mix named and positional parameters, it makes me dizzy.', detail=detail, type="commandException"); } @@ -283,61 +283,61 @@ component accessors="true" singleton { // Add command to the top of the stack instance.callStack.prepend( { commandInfo : commandInfo, environment : {} } ); - - // Start the try as soon as we prepend to the call stack so any errors from here on out, even parsing the params, will + + // Start the try as soon as we prepend to the call stack so any errors from here on out, even parsing the params, will // correct remove this call from the stack in the finally block. try { - + // If we're using postitional params, convert them to named if( arrayLen( parameterInfo.positionalParameters ) ){ parameterInfo.namedParameters = convertToNamedParameters( parameterInfo.positionalParameters, commandParams ); } - + // Merge flags into named params mergeFlagParameters( parameterInfo ); - + // Add in defaults for every possible alias of this command [] - .append( commandInfo.commandReference.originalName ) + .append( commandInfo.commandReference.originalName.replace( '.', ' ', 'all' ) ) .append( commandInfo.commandReference.aliases, true ) .each( function( thisName ) { addDefaultParameters( thisName, parameterInfo ); } ); - + // Make sure we have all required params. parameterInfo.namedParameters = ensureRequiredParams( parameterInfo.namedParameters, commandParams ); - + interceptorService.announceInterception( 'preCommandParamProcess', { commandInfo=commandInfo, parameterInfo=parameterInfo } ); - + // System settings need evaluated prior to expressions! evaluateSystemSettings( parameterInfo ); evaluateExpressions( parameterInfo ); - + // Combine params like command foo:bar=1 foo:baz=2 foo:bum=3 combineColonParams( parameterInfo ); - + // Create globbing patterns createGlobs( parameterInfo, commandParams ); - + // Ensure supplied params match the correct type validateParams( parameterInfo.namedParameters, commandParams ); - + interceptorService.announceInterception( 'preCommand', { commandInfo=commandInfo, parameterInfo=parameterInfo } ); - + // Tells us if we are going to capture the output of this command and pass it to another // Used for our workaround to switch if the "run" command pipes to the terminal or not // If there are more commands in the chain and we are going to pipe or redirect to them if( arrayLen( commandChain ) > i && listFindNoCase( '|,>,>>', commandChain[ i+1 ].originalLine ) ) { captureOutput = true; } - - // This is my workaround to "smartly" capture the output of the run command if we're piping it or + + // This is my workaround to "smartly" capture the output of the run command if we're piping it or // nesting it as an expression, etc. if( commandInfo.commandReference.originalName == 'run' && captureOutput ) { parameterInfo.namedParameters.interactive=false; - } - - + } + + // Run the command var result = commandInfo.commandReference.CFC.run( argumentCollection = parameterInfo.namedParameters ); lastCommandErrored = commandInfo.commandReference.CFC.hasError(); @@ -345,14 +345,14 @@ component accessors="true" singleton { lastCommandErrored = true; // If this command didn't already set a failing exit code... if( commandInfo.commandReference.CFC.getExitCode() == 0 ) { - + // Go ahead and set one for it. The shell will inherit it below in the finally block. if( val( e.errorCode ?: 0 ) > 0 ) { commandInfo.commandReference.CFC.setExitCode( e.errorCode ); } else { commandInfo.commandReference.CFC.setExitCode( 1 ); } - + } // Dump out anything the command had printed so far @@ -398,7 +398,7 @@ component accessors="true" singleton { local.result = interceptData.result; } // End loop over command chain - + return local.result; } @@ -552,7 +552,7 @@ component accessors="true" singleton { function resolveCommand( required string line, boolean forCompletion=false ){ // Turn the users input into an array of tokens var tokens = parser.tokenizeInput( line ); - + return resolveCommandTokens( tokens, line, forCompletion ); } @@ -561,7 +561,7 @@ component accessors="true" singleton { * @tokens.hint An array containing the command and parameters that the user entered **/ function resolveCommandTokens( required array tokens, string rawLine=tokens.toList( ' ' ), boolean forCompletion=false ){ - + // This will hold the command chain. Usually just a single command, // but a pipe ("|") will chain together commands and pass the output of one along as the input to the next var commandsToResolve = breakTokensIntoChain( tokens ); @@ -615,7 +615,7 @@ component accessors="true" singleton { * run "cmd /c dir" */ if( tokens.len() > 1 && tokens.first() == 'run' ) { - + var tokens2 = tokens[ 2 ]; // Escape any regex metacharacters in the pattern tokens2 = replace( tokens2, '\', '\\', 'all' ); @@ -628,7 +628,7 @@ component accessors="true" singleton { tokens2 = replace( tokens2, '+', '\+', 'all' ); tokens2 = replace( tokens2, '{', '\{', 'all' ); tokens2 = replace( tokens2, '}', '\}', 'all' ); - + tokens = [ 'run', // Strip off "!" or "run" at the start of the raw line. @@ -638,7 +638,7 @@ component accessors="true" singleton { // To fix it though I'd need to substantially change how tokenizing works rawLine.reReplaceNoCase( '^(.*?)?[\s]*(run |!)[\s]*(#tokens2#.*)', '\3' ) ]; - + // The run command "eats" end entire rest of the line, so stop processing the command chain. stopProcessingLine = true; } @@ -674,7 +674,7 @@ component accessors="true" singleton { if( !structKeyExists( results.commandReference, token ) ){ break; } - + // If this is for command tab completion, don't select the command if there are two commands at the same level that start wtih this string // This check only runs if we've matched all the entered tokens and there is no trailing space. if( forCompletion && pos == tokens.len() ) { @@ -721,12 +721,12 @@ component accessors="true" singleton { } commandChain.append( results ); - + // Quit here if we're done with this command line if( stopProcessingLine ) { break; } - + } // end loop over commands to resolve // Return command chain @@ -940,7 +940,7 @@ component accessors="true" singleton { * @fullCFCPath the full CFC path * @commandName the command name */ - private struct function createCommandData( required fullCFCPath, required commandName ){ + private struct function createCommandData( required fullCFCPath, required commandName ){ // Get from cache or produce on demand var commandMD = metadataCache.getOrSet( fullCFCPath, @@ -948,7 +948,7 @@ component accessors="true" singleton { return wirebox.getUtil().getInheritedMetaData( fullCFCPath ); } ); - + // Set up of command data var commandData = { fullCFCPath = arguments.fullCFCPath, @@ -960,12 +960,12 @@ component accessors="true" singleton { excludeFromHelp = commandMD.excludeFromHelp ?: false, commandMD = commandMD }; - + // Fix for CFCs with no hint, they inherit this from the Lucee base compnent. if( commandData.hint == 'This is the Base Component' ) { commandData.hint = ''; } - + // check functions if( structKeyExists( commandMD, 'functions' ) ){ // Capture the command's parameters @@ -1082,7 +1082,7 @@ component accessors="true" singleton { // Overwrite it with an actual Globber instance seeded with the original canonical path as the pattern. var originalPath = parameterInfo.namedParameters[ paramName ]; var newPath = fileSystemUtil.resolvePath( originalPath ); - + parameterInfo.namedParameters[ paramName ] = wirebox.getInstance( 'Globber' ) .setPattern( newPath ); } @@ -1159,7 +1159,7 @@ component accessors="true" singleton { } ); } - + /** * Get the array of commands being executed. From 74f9b641e38beac9a9ca3cd3f8d6695065c2401f Mon Sep 17 00:00:00 2001 From: Pete Freitag Date: Wed, 16 Oct 2019 18:55:56 -0400 Subject: [PATCH 012/102] Add server ID and home to verbose server status (#208) This might be ok to move up to non-verbose, but I often want to know where my home directory is for the server, and I expect it would be shown when I run `server status` --- .../modules_app/server-commands/commands/server/status.cfc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cfml/system/modules_app/server-commands/commands/server/status.cfc b/src/cfml/system/modules_app/server-commands/commands/server/status.cfc index 0165b914d..9dcdd19b4 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/status.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/status.cfc @@ -153,6 +153,10 @@ component aliases='status,server info' { if( arguments.verbose ) { + print.indentedLine( 'ID: ' & thisServerInfo.id ); + + print.indentedLine( 'Server Home: ' & thisServerInfo.serverHome ); + print.indentedLine( trim( thisServerInfo.statusInfo.command ) ); // Put each --arg or -arg on a new line var args = trim( reReplaceNoCase( thisServerInfo.statusInfo.arguments, ' (-|"-)', cr & '\1', 'all' ) ); From 6ed2bd7f40e0ac0c03fe22a63933464cd2edb34d Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 21 Oct 2019 10:00:16 -0500 Subject: [PATCH 013/102] Add secure rewrite file --- src/cfml/system/config/urlrewrite-secure.xml | 59 ++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/cfml/system/config/urlrewrite-secure.xml diff --git a/src/cfml/system/config/urlrewrite-secure.xml b/src/cfml/system/config/urlrewrite-secure.xml new file mode 100644 index 000000000..20f9f35ff --- /dev/null +++ b/src/cfml/system/config/urlrewrite-secure.xml @@ -0,0 +1,59 @@ + + + + + + Deny TRACE/TRACK HTTP Verb + TRACE|TRACK + 403 + null + + + + Deny Administrative Access + + /(CFIDE/administrator|CFIDE/adminapi|CFIDE/AIR|CFIDE/appdeployment|CFIDE/cfclient|CFIDE/classes|CFIDE/componentutils|CFIDE/debug|CFIDE/images|CFIDE/orm|CFIDE/portlets|CFIDE/scheduler|CFIDE/ServerManager|CFIDE/services|CFIDE/websocket|CFIDE/wizards|lucee/admin)/.* + ^/(.+)$ + 404 + /404.html + + + + Deny "hidden" files + .*/\..* + ^/(.+)$ + 404 + /404.html + + + + Deny common config files + .*/(box.json|server.json|web.config|urlrewrite.xml|package.json|package-lock.json|Gulpfile.js|CFIDE/multiservermonitor-access-policy.xml|CFIDE/probe.cfm) + ^/(.+)$ + 404 + /404.html + + + + + Generic Front-Controller URLs + + ^/(flex2gateway|flashservices/gateway|messagebroker|lucee|rest|cfide|CFIDE|cfformgateway|jrunscripts|cf_scripts|mapping-tag|CFFileServlet)/.* + + ^/tuckey-status + + ^/pms$ + + ^/favicon.ico + + ^/.*\.cf(m|ml)/.* + + + + + ^/(.+)$ + /index.cfm/$1 + + + From d5ba6ac28cdc4c43c9beb5ac50ef2815642d94a9 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 21 Oct 2019 10:00:29 -0500 Subject: [PATCH 014/102] move deletes up --- src/java/cliloader/LoaderCLIMain.java | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/java/cliloader/LoaderCLIMain.java b/src/java/cliloader/LoaderCLIMain.java index b6e206e09..225df376f 100644 --- a/src/java/cliloader/LoaderCLIMain.java +++ b/src/java/cliloader/LoaderCLIMain.java @@ -630,6 +630,17 @@ public static ArrayList< String > initialize( String[] arguments ) throws IOExce || updateLibs ) { log.info( "Library path: " + libDir ); log.info( "Initializing libraries -- this will only happen once, and takes a few seconds..." ); + + // OSGI can be grumpy on uprade with comppeting bundles. Start fresh + if( cfmlBundlesDir.exists() ) { + log.info( "Cleaning old OSGI Bundles..." ); + Util.deleteDirectory( cfmlBundlesDir ); + } + // OSGI can be grumpy on uprade with comppeting bundles. Start fresh + if( cfmlSystemDir.exists() ) { + log.info( "Cleaning old Felix Cache..." ); + Util.deleteDirectory( cfmlFelixCacheDir ); + } // Try to delete the Runwar jar first since it's the most likely to be locked. // If it fails, this method will just abort before we get any farther into deleting stuff. @@ -643,16 +654,6 @@ public static ArrayList< String > initialize( String[] arguments ) throws IOExce if( cfmlSystemDir.exists() ) { Util.deleteDirectory( cfmlSystemDir ); } - - // OSGI can be grumpy on uprade with comppeting bundles. Start fresh - if( cfmlBundlesDir.exists() ) { - log.info( "Cleaning old OSGI Bundles..." ); - Util.deleteDirectory( cfmlBundlesDir ); - } - // OSGI can be grumpy on uprade with comppeting bundles. Start fresh - if( cfmlSystemDir.exists() ) { - Util.deleteDirectory( cfmlFelixCacheDir ); - } Util.unzipInteralZip( classLoader, CFML_ZIP_PATH, cfmlDir, debug ); From 930f6dca7b92806e08471dc15607aaf58312d06c Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 21 Oct 2019 21:29:16 +0200 Subject: [PATCH 015/102] Add excludes support to testbox run command (#207) * Add excludes support to testbox run command * Add excludes to box.json.txt * Add excludes and labels to `box.json` schema --- src/cfml/system/config/box.json.txt | 3 ++- src/cfml/system/config/box.schema.json | 26 ++++++++++++++++++- .../testbox-commands/commands/testbox/run.cfc | 3 +++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/cfml/system/config/box.json.txt b/src/cfml/system/config/box.json.txt index f0e47bbf6..1c8583f44 100644 --- a/src/cfml/system/config/box.json.txt +++ b/src/cfml/system/config/box.json.txt @@ -63,6 +63,7 @@ } ], "labels":"", + "excludes":"", "reporter":"", "bundles":"", "recurse":true, @@ -75,4 +76,4 @@ "watchPaths":"**.cfc", "options":{} } -} \ No newline at end of file +} diff --git a/src/cfml/system/config/box.schema.json b/src/cfml/system/config/box.schema.json index 397d016bc..285d860dd 100644 --- a/src/cfml/system/config/box.schema.json +++ b/src/cfml/system/config/box.schema.json @@ -378,8 +378,32 @@ } }, "format": "uri" + }, + "labels": { + "title": "Labels List", + "description": "A list of labels to only include when running the tests", + "type": [ + "string", + "array" + ], + "items": { + "description": "A label a spec must have to be ran", + "type": "string" + } + } + "excludes": { + "title": "Exclude List", + "description": "A list of labels to exclude when running the tests", + "type": [ + "string", + "array" + ], + "items": { + "description": "A label to be excluded from the test run", + "type": "string" + } } } } } -} \ No newline at end of file +} diff --git a/src/cfml/system/modules_app/testbox-commands/commands/testbox/run.cfc b/src/cfml/system/modules_app/testbox-commands/commands/testbox/run.cfc index 3fc080c25..22cc0c0e8 100644 --- a/src/cfml/system/modules_app/testbox-commands/commands/testbox/run.cfc +++ b/src/cfml/system/modules_app/testbox-commands/commands/testbox/run.cfc @@ -64,6 +64,7 @@ component { * @recurse Recurse the directory mapping or not, by default it does * @reporter The type of reporter to use for the results, by default is uses our 'simple' report. You can pass in a core reporter string type or a class path to the reporter to use. * @labels The list of labels that a suite or spec must have in order to execute. + * @excludes The list of labels that a suite or spec must not have in order to execute. * @options Add adhoc URL options to the runner as options:name=value options:name2=value2 * @testBundles A list or array of bundle names that are the ones that will be executed ONLY! * @testSuites A list of suite names that are the ones that will be executed ONLY! @@ -78,6 +79,7 @@ component { boolean recurse, string reporter, string labels, + string excludes, struct options={}, string testBundles, string testSuites, @@ -132,6 +134,7 @@ component { "bundles" : "", "directory" : "", "labels" : "", + "excludes" : "", "testBundles" : "", "testSuites" : "", "testSpecs" : "", From 00ac155825a5cbf8b6f408d05d207bd246c54245 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 28 Oct 2019 09:50:33 -0500 Subject: [PATCH 016/102] COMMANDBOX-1057 --- src/cfml/system/box.json | 4 +- src/cfml/system/modules/globber/box.json | 6 +- .../system/modules/globber/models/Globber.cfc | 187 ++++++++++++++++-- .../globber/models/PathPatternMatcher.cfc | 8 +- .../system-commands/commands/dir.cfc | 67 +++++-- .../system-commands/commands/history.cfc | 2 +- src/cfml/system/services/CommandService.cfc | 2 +- src/cfml/system/services/ServerService.cfc | 2 +- 8 files changed, 240 insertions(+), 38 deletions(-) diff --git a/src/cfml/system/box.json b/src/cfml/system/box.json index 7798afff1..e69f0d485 100644 --- a/src/cfml/system/box.json +++ b/src/cfml/system/box.json @@ -6,7 +6,7 @@ "dependencies":{ "string-similarity":"^1.0.0", "semver":"^1.2.3", - "globber":"^2.0.0", + "globber":"^3.0.1", "JSONPrettyPrint":"^1.0.0", "propertyFile":"^1.0.9" }, @@ -14,7 +14,7 @@ "installPaths":{ "string-similarity":"modules\\string-similarity", "semver":"modules/semver/", - "globber":"modules\\globber", + "globber":"modules/globber/", "JSONPrettyPrint":"modules\\JSONPrettyPrint", "propertyFile":"modules\\propertyFile" } diff --git a/src/cfml/system/modules/globber/box.json b/src/cfml/system/modules/globber/box.json index ffd8fa971..be0203cd1 100644 --- a/src/cfml/system/modules/globber/box.json +++ b/src/cfml/system/modules/globber/box.json @@ -1,8 +1,8 @@ { "name":"Globber", - "version":"2.1.2", + "version":"3.0.2", "author":"Brad Wood", - "location":"Ortus-Solutions/globber#v2.1.2", + "location":"Ortus-Solutions/globber#v3.0.2", "homepage":"https://github.com/Ortus-Solutions/globber/", "documentation":"https://github.com/Ortus-Solutions/globber/", "repository":{ @@ -25,7 +25,7 @@ "scripts":{ "postVersion":"package set location='Ortus-Solutions/globber#v`package version`'", "onRelease":"forgebox use ortus && publish", - "postPublish":"!git push && !git push --tags" + "postPublish":"!git push --follow-tags" }, "ignore":[ "**/.*", diff --git a/src/cfml/system/modules/globber/models/Globber.cfc b/src/cfml/system/modules/globber/models/Globber.cfc index 70a2096ff..0fd3240a9 100644 --- a/src/cfml/system/modules/globber/models/Globber.cfc +++ b/src/cfml/system/modules/globber/models/Globber.cfc @@ -16,7 +16,9 @@ component accessors="true" { property name='pathPatternMatcher' inject='pathPatternMatcher@globber'; /** The file globbing pattern to match. */ - property name='pattern' default=''; + property name='pattern'; + /** The file globbing pattern NOT to match. */ + property name='excludePattern'; /** query of real file system resources that match the pattern */ property name='matchQuery'; /** Return matches as a query instead of an array */ @@ -29,6 +31,8 @@ component accessors="true" { function init() { variables.format = 'array'; + variables.pattern = []; + variables.excludePattern = []; return this; } @@ -58,12 +62,88 @@ component accessors="true" { /** * Override setter to ensure consistent slashe in pattern + * Can be list of patterns or array of patterns. + * Empty patterns will be ignored */ - function setPattern( required string pattern ) { - variables.pattern = pathPatternMatcher.normalizeSlashes( arguments.pattern ); + function setPattern( required any pattern ) { + if( isSimpleValue( arguments.pattern ) ) { + arguments.pattern = listToArray( arguments.pattern ); + } + arguments.pattern = arguments.pattern.map( function( p ) { + return pathPatternMatcher.normalizeSlashes( arguments.p ); + }).filter( function( p ){ + return len( arguments.p ); + } ); + variables.pattern = arguments.pattern; + return this; + } + + /** + * Add addiional pattern to process + */ + function addPattern( required string pattern ) { + if( len( arguments.pattern ) ) { + variables.pattern.append( arguments.pattern ); + } + return this; + } + + /** + * Always returns a string which is a list of patterns + */ + function getPattern() { + return variables.pattern.toList(); + } + + /** + * + */ + function getPatternArray() { + return variables.pattern; + } + + + /** + * Can be list of excludePatterns or array of excludePatterns. + * Empty excludePatterns will be ignored + */ + function setExcludePattern( required any excludePattern ) { + if( isSimpleValue( arguments.excludePattern ) ) { + arguments.excludePattern = listToArray( arguments.excludePattern ); + } + arguments.excludePattern = arguments.excludePattern.map( function( p ) { + return pathPatternMatcher.normalizeSlashes( arguments.p ); + }).filter( function( p ){ + return len( arguments.p ); + } ); + variables.excludePattern = arguments.excludePattern; return this; } + /** + * Add addiional excludePattern to process + */ + function addExcludePattern( required string excludePattern ) { + if( len( arguments.excludePattern ) ) { + variables.excludePattern.append( arguments.excludePattern ); + } + return this; + } + + /** + * Always returns a string which is a list of excludePatterns + */ + function getExcludePattern() { + return variables.excludePattern.toList(); + } + + /** + * + */ + function getExcludePatternArray() { + return variables.excludePattern; + } + /** * Pass a closure to this function to have it * applied to each paths matched by the pattern. @@ -108,12 +188,66 @@ component accessors="true" { * Load matching file from the file system */ private function process() { - local.thisPattern = pathPatternMatcher.normalizeSlashes( getPattern() ); + var patterns = getPatternArray(); - if( !thisPattern.len() ) { + if( !patterns.len() ) { throw( 'Cannot glob empty pattern.' ); } + + for( var thisPattern in patterns ) { + var results = processPattern( thisPattern ); + // First one in just gets set + if( isNull( getMatchQuery() ) ) { + setMatchQuery( results ); + // merge remaining patterns + } else { + var previousMatch = getMatchQuery(); + cfquery( dbtype="query" ,name="local.newMatchQuery" ) { + writeOutput( 'SELECT * FROM results UNION SELECT * FROM previousMatch ' ); + } + + // UNION isn't removing dupes on Lucee so doing second select here for that purpose. + cfquery( dbtype="query" ,name="local.newMatchQuery" ) { + writeOutput( 'SELECT DISTINCT * FROM newMatchQuery ' ); + if( len( getSort() ) ) { + writeOutput( 'ORDER BY #getCleanSort()#' ); + } + } + + setMatchQuery( local.newMatchQuery ); + } + } + + if( patterns.len() > 1 ) { + var dirs = queryColumnData( getMatchQuery(), 'directory' ); + var lookups = {}; + dirs.each( function( dir ) { + // Account for *nix paths & Windows UNC network shares + var prefix = ''; + if( dir.startsWith( '/' ) ) { + prefix = '/'; + } else if( dir.startsWith( '\\' ) ) { + prefix = '//'; + } + evaluate( 'lookups["#prefix##dir.listChangeDelims( '"]["', '/\' )#"]={}' ); + } ); + var findRoot = function( lookups ){ + if( lookups.count() == 1 ) { + return lookups.keyList() & '/' & findRoot( lookups[ lookups.keyList() ] ); + } else { + return ''; + } + } + setBaseDir( findRoot( lookups ) ); + } + + } + + private function processPattern( string pattern ) { + + local.thisPattern = pathPatternMatcher.normalizeSlashes( arguments.pattern ); + // To optimize this as much as possible, we want to get a directory listing as deep as possible so we process a few files as we can. // Find the deepest folder that doesn't have a wildcard in it. var baseDir = ''; @@ -144,11 +278,16 @@ component accessors="true" { if( thisPattern contains '**' ) { recurse = true; } - - setMatchQuery( - directoryList ( + + setBaseDir( baseDir & ( baseDir.endsWith( '/' ) ? '' : '/' ) ); + + return directoryList ( filter=function( path ){ - if( pathPatternMatcher.matchPattern( thisPattern, path & ( directoryExists( path ) ? '/' : '' ), true ) ) { + var thisPath = path & ( directoryExists( path ) ? '/' : '' ); + if( pathPatternMatcher.matchPattern( thisPattern, thisPath, true ) ) { + if( getExcludePatternArray().len() && pathPatternMatcher.matchPatterns( getExcludePatternArray(), thisPath, true ) ) { + return false; + } return true; } return false; @@ -157,11 +296,33 @@ component accessors="true" { recurse=local.recurse, path=baseDir, sort=getSort() - ) - ); - setBaseDir( baseDir ); - + ); + } + /** + * The sort function in CFDirectory will simply ignore invalid sort columns so I'm mimicing that here, as much as I dislike it. + * The sort should be in the format of "col asc, col2 desc, col3, col4" like a SQL order by + * If any of the coluns or sort directions don't look right, just bail and return the default sort. + */ + function getCleanSort() { + // Loop over each sort item + for( var item in listToArray( getSort() ) ) { + // Validate column name + if( !listFindNoCase( 'name,directory,size,type,dateLastModified,attributes,mode', trim( item.listFirst( ' ' ) ) ) ) { + return 'type, name'; + } + // Validate sort direction + if( item.listLen( ' ' ) == 2 && !listFindNoCase( 'asc,desc', trim( item.listLast( ' ' ) ) ) ) { + return 'type, name'; + } + // Ensure no more than 2 tokens + if( item.listLen( ' ' ) > 2 ) { + return 'type, name'; + } + } + // Ok, everything passes. + return getSort(); + } } diff --git a/src/cfml/system/modules/globber/models/PathPatternMatcher.cfc b/src/cfml/system/modules/globber/models/PathPatternMatcher.cfc index e57dc08bb..0976d26ee 100644 --- a/src/cfml/system/modules/globber/models/PathPatternMatcher.cfc +++ b/src/cfml/system/modules/globber/models/PathPatternMatcher.cfc @@ -38,7 +38,7 @@ component accessors="true" singleton { * @path The file system path to test. Can be a file or directory. Direcories MUST end with a trailing slash * @exact True if the full path needs to match. False to match inside a path */ - boolean function matchPattern( required string pattern, required string path, boolean exact=false) { + boolean function matchPattern( required string pattern, required string path, boolean exact=false ) { // Normalize slashes // This will turn a Windows UNC path into //server, but it will at least be consitent across pattern and path arguments.pattern = replace( arguments.pattern, '\', '/', 'all' ); @@ -107,17 +107,17 @@ component accessors="true" singleton { * @patterns.hint An array of patterns to match against the path * @path.hint The file system path to test. Can be a file or directory. Direcories MUST end with a trailing slash */ - boolean function matchPatterns( required array patterns, required string path ){ + boolean function matchPatterns( required array patterns, required string path, boolean exact=false ){ var matched = false; for( var pattern in arguments.patterns ) { if ( isExclusion( pattern ) ) { var patternWithoutBang = mid( pattern, 2, len( pattern ) - 1 ); - if ( matchPattern( patternWithoutBang, arguments.path ) ) { + if ( matchPattern( patternWithoutBang, arguments.path, arguments.exact ) ) { matched = false; } } else { - if ( matchPattern( pattern, arguments.path ) ) { + if ( matchPattern( pattern, arguments.path, arguments.exact ) ) { matched = true; } } diff --git a/src/cfml/system/modules_app/system-commands/commands/dir.cfc b/src/cfml/system/modules_app/system-commands/commands/dir.cfc index 67769f166..116ba2d49 100644 --- a/src/cfml/system/modules_app/system-commands/commands/dir.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/dir.cfc @@ -5,39 +5,80 @@ * dir samples/ * {code} * . - * Use the "recurse" paramater to show all nested files and folders. + * File globbing patterns can be used to filter results. Can also be a list + * . + * {code:bash} + * dir **.cfc,*.cfm + * {code} + * . + * File globbing patterns can be used to exclude results. Can also be a list + * . + * {code:bash} + * dir paths=modules excludePath=**.md --recurse + * {code} + * . + * Use the "recurse" parameter to show all nested files and folders. * . * {code:bash} * dir samples/ --recurse * {code} + * . + * Ordering results is in format of an ORDER BY SQL clause. Invalid sorts are ignored. + * . + * {code:bash} + * dir samples "directory asc, name desc" + * {code} * **/ component aliases="ls,ll,directory" { /** - * @directory.hint The directory to list the contents of or a file Globbing path to filter on + * @directory.hint The directory to list the contents of or a list of file Globbing path to filter on + * @excludePath.hint A list of file glob patterns to exclude + * @sort.hint Sort columns and direction. name, directory, size, type, dateLastModified, attributes, mode * @recurse.hint Include nested files and folders * @simple.hint Output only path names and nothing else. **/ - function run( Globber directory=globber( getCWD() ), Boolean recurse=false, boolean simple=false ) { + function run( Globber paths=globber( getCWD() ), sort='type, name', string excludePaths='', boolean recurse=false, boolean simple=false ) { + + // Backwards compat for old parameter name + if( arguments.keyExists( 'directory' ) && arguments.directory.len() ) { + paths.setPattern( fileSystemUtil.resolvePath( arguments.directory ) ); + } // If the user gives us an existing directory foo, change it to the // glob pattern foo/* or foo/** if doing a recursive listing. - if( directoryExists( directory.getPattern() ) ){ - directory.setPattern( directory.getPattern() & '*' & ( recurse ? '*' : '' ) ); - } - + paths.setPattern( + paths.getPatternArray().map( (p) => { + if( pathsExists( p ) ){ + return p & '*' & ( recurse ? '*' : '' ); + } + return p; + } ) + ); + // TODO: Add ability to re-sort this based on user input - var results = directory + + var excludePath = excludePath.listMap( (p) => { + p = fileSystemUtil.resolvePath( p ) + if( directoryExists( p ) ){ + return p & '*' & ( recurse ? '*' : '' ); + } + return p; + } ); + + var results = paths + .setExcludePattern( excludePath ) .asQuery() + .withSort( sort ) .matches(); - + for( var x=1; x lte results.recordcount; x++ ) { if( simple ) { print.line( - cleanRecursiveDir( arguments.directory.getBaseDir(), results.directory[ x ] ) + cleanRecursiveDir( arguments.paths.getBaseDir(), results.paths[ x ] & '/' ) & results.name[ x ] & ( results.type[ x ] == "Dir" ? "/" : "" ) ); @@ -61,8 +102,8 @@ component aliases="ls,ll,directory" { colorPath( cleanRecursiveDir( - arguments.directory.getBaseDir(), - results.directory[ x ] + arguments.paths.getBaseDir(), + results.paths[ x ] & '/' ) & results.name[ x ] & ( results.type[ x ] == "Dir" ? "/" : "" @@ -82,7 +123,7 @@ component aliases="ls,ll,directory" { */ private function cleanRecursiveDir( required directory, required incoming, type ){ var prefix = ( replacenocase( expandPath( arguments.incoming ), expandPath( arguments.directory ), "" ) ); - return ( len( prefix ) ? reReplace( prefix, "^(/|\\)", "" ) & "/" : "" ); + return ( len( prefix ) ? reReplace( prefix, "^(/|\\)", "" ) : "" ); } private function colorPath( name, type ){ diff --git a/src/cfml/system/modules_app/system-commands/commands/history.cfc b/src/cfml/system/modules_app/system-commands/commands/history.cfc index 15a8645f8..386bf316e 100644 --- a/src/cfml/system/modules_app/system-commands/commands/history.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/history.cfc @@ -12,7 +12,7 @@ * {code} * . * There are separate histories for commands, script REPL and tag REPL. - * Use the "type" paramater to specifiy which history you want to view or clear. + * Use the "type" parameter to specifiy which history you want to view or clear. * . * {code:bash} * history type=command diff --git a/src/cfml/system/services/CommandService.cfc b/src/cfml/system/services/CommandService.cfc index 7e91f111a..25a2d6125 100644 --- a/src/cfml/system/services/CommandService.cfc +++ b/src/cfml/system/services/CommandService.cfc @@ -1081,7 +1081,7 @@ component accessors="true" singleton { // Overwrite it with an actual Globber instance seeded with the original canonical path as the pattern. var originalPath = parameterInfo.namedParameters[ paramName ]; - var newPath = fileSystemUtil.resolvePath( originalPath ); + var newPath = originalPath.listMap( (p) => fileSystemUtil.resolvePath( p ) ); parameterInfo.namedParameters[ paramName ] = wirebox.getInstance( 'Globber' ) .setPattern( newPath ); diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 25c1307f2..a0c53cd61 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -1256,7 +1256,7 @@ component accessors="true" singleton { } } } - + // Conjoin standard error and output for convenience. processBuilder.redirectErrorStream( true ); // Kick off actual process From d126427bd19579a5438c11c2e5842aacdb7c7cf7 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 28 Oct 2019 09:54:21 -0500 Subject: [PATCH 017/102] COMMANDBOX-1058 --- src/cfml/system/services/PackageService.cfc | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/cfml/system/services/PackageService.cfc b/src/cfml/system/services/PackageService.cfc index a8aec4662..88661a8e6 100644 --- a/src/cfml/system/services/PackageService.cfc +++ b/src/cfml/system/services/PackageService.cfc @@ -374,9 +374,9 @@ component accessors="true" singleton { // If the box.json had an explicit override for the install directory, then we're just going to use it directly if( artifactDescriptor.createPackageDirectory || structKeyExists( containerBoxJSON.installPaths, packageName ) ) { installDirectory &= '/#packageDirectory#'; - // If we're dumping in the root and the install dir is already a package then ignore box.json or it will overwrite the existing one + // If we're dumping in the root and the install dir is already another package then ignore box.json or it will overwrite the existing one // If the directory wasn't already a package, still save so our box.json gets install paths added - } else if( isPackage( installDirectory ) ) { + } else if( isPackage( installDirectory ) && readPackageDescriptor( installDirectory ).slug != packageName ) { ignorePatterns.append( '/box.json' ); } @@ -392,7 +392,8 @@ component accessors="true" singleton { } // Check to see if package has already been installed. This check can only be performed for packages that get installed in their own directory. - if( artifactDescriptor.createPackageDirectory && directoryExists( installDirectory ) ){ + // OR if the install dir has a box.json that is the package being installed. + if( directoryExists( installDirectory ) && ( artifactDescriptor.createPackageDirectory || readPackageDescriptor( installDirectory ).slug == packageName ) ){ var uninstallFirst = false; // Make sure the currently installed version is older than what's being requested. If there's a new version, install it anyway. @@ -404,6 +405,10 @@ component accessors="true" singleton { } else if( arguments.force ) { job.addLog( "Package already installed but you forced a reinstall." ); uninstallFirst = true; + // Check for empty directories that sometimes get left behind, but really shouldn't count as the package actually being there. + } else if( !directorylist( installDirectory ).len() ) { + job.addLog( "Package directory exists, but is empty so we're going to assume it's not really installed." ); + uninstallFirst = true; } else { // cleanup tmp tempDir = fileSystemUtil.resolvePath( tempDir ); @@ -421,7 +426,7 @@ component accessors="true" singleton { return true; } - if( uninstallFirst ) { + if( uninstallFirst && artifactDescriptor.createPackageDirectory ) { job.addWarnLog( "Uninstalling first to get a fresh slate..." ); var params = { From 0eefe624c6d651635a09b780fb654fc67054b429 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 28 Oct 2019 11:14:46 -0500 Subject: [PATCH 018/102] Typos and performance boost --- .../modules_app/system-commands/commands/dir.cfc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/cfml/system/modules_app/system-commands/commands/dir.cfc b/src/cfml/system/modules_app/system-commands/commands/dir.cfc index 116ba2d49..0a7cf2f9d 100644 --- a/src/cfml/system/modules_app/system-commands/commands/dir.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/dir.cfc @@ -14,7 +14,7 @@ * File globbing patterns can be used to exclude results. Can also be a list * . * {code:bash} - * dir paths=modules excludePath=**.md --recurse + * dir paths=modules excludePaths=**.md --recurse * {code} * . * Use the "recurse" parameter to show all nested files and folders. @@ -34,7 +34,7 @@ component aliases="ls,ll,directory" { /** * @directory.hint The directory to list the contents of or a list of file Globbing path to filter on - * @excludePath.hint A list of file glob patterns to exclude + * @excludePaths.hint A list of file glob patterns to exclude * @sort.hint Sort columns and direction. name, directory, size, type, dateLastModified, attributes, mode * @recurse.hint Include nested files and folders * @simple.hint Output only path names and nothing else. @@ -50,7 +50,7 @@ component aliases="ls,ll,directory" { // glob pattern foo/* or foo/** if doing a recursive listing. paths.setPattern( paths.getPatternArray().map( (p) => { - if( pathsExists( p ) ){ + if( directoryExists( p ) ){ return p & '*' & ( recurse ? '*' : '' ); } return p; @@ -59,7 +59,7 @@ component aliases="ls,ll,directory" { // TODO: Add ability to re-sort this based on user input - var excludePath = excludePath.listMap( (p) => { + excludePaths = excludePaths.listMap( (p) => { p = fileSystemUtil.resolvePath( p ) if( directoryExists( p ) ){ return p & '*' & ( recurse ? '*' : '' ); @@ -68,7 +68,7 @@ component aliases="ls,ll,directory" { } ); var results = paths - .setExcludePattern( excludePath ) + .setExcludePattern( excludePaths ) .asQuery() .withSort( sort ) .matches(); @@ -78,7 +78,7 @@ component aliases="ls,ll,directory" { if( simple ) { print.line( - cleanRecursiveDir( arguments.paths.getBaseDir(), results.paths[ x ] & '/' ) + cleanRecursiveDir( arguments.paths.getBaseDir(), results.directory[ x ] & '/' ) & results.name[ x ] & ( results.type[ x ] == "Dir" ? "/" : "" ) ); @@ -103,7 +103,7 @@ component aliases="ls,ll,directory" { colorPath( cleanRecursiveDir( arguments.paths.getBaseDir(), - results.paths[ x ] & '/' + results.directory[ x ] & '/' ) & results.name[ x ] & ( results.type[ x ] == "Dir" ? "/" : "" @@ -122,7 +122,7 @@ component aliases="ls,ll,directory" { * Cleanup directory recursive nesting */ private function cleanRecursiveDir( required directory, required incoming, type ){ - var prefix = ( replacenocase( expandPath( arguments.incoming ), expandPath( arguments.directory ), "" ) ); + var prefix = ( replacenocase( fileSystemUtil.normalizeSlashes( arguments.incoming ), fileSystemUtil.normalizeSlashes( arguments.directory ), "" ) ); return ( len( prefix ) ? reReplace( prefix, "^(/|\\)", "" ) : "" ); } From 7c52d0e1c8c032ca9258560ca2a8e8445dd3b10d Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 28 Oct 2019 11:16:46 -0500 Subject: [PATCH 019/102] COMMANDBOX-1059 --- src/cfml/system/util/PrintBuffer.cfc | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/cfml/system/util/PrintBuffer.cfc b/src/cfml/system/util/PrintBuffer.cfc index a63ede115..7e0871c46 100644 --- a/src/cfml/system/util/PrintBuffer.cfc +++ b/src/cfml/system/util/PrintBuffer.cfc @@ -19,9 +19,10 @@ component accessors="true" extends="Print"{ /** * Result buffer */ - property name="result" default=""; + property name="result"; function init(){ + variables.result = createObject( 'java', 'java.lang.StringBuilder' ).init( '' ); setObjectID( createUUID() ); return this; } @@ -41,14 +42,18 @@ component accessors="true" extends="Print"{ // Reset the result function clear(){ - variables.result = ''; + variables.result.setLength(0); + } + + function getResult() { + return variables.result.toString(); } // Proxy through any methods to the actual print helper function onMissingMethod( missingMethodName, missingMethodArguments ){ // Don't modify the buffer if it's being printed lock name='printBuffer-#getObjectID()#' type="readonly" timeout="20" { - variables.result &= super.onMissingMethod( arguments.missingMethodName, arguments.missingMethodArguments ); + variables.result.append( super.onMissingMethod( arguments.missingMethodName, arguments.missingMethodArguments ) ); return this; } } From 751b1f7fe9daf71c4b3eb354e98ff34d4852848e Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 28 Oct 2019 11:23:30 -0500 Subject: [PATCH 020/102] COMMANDBOX-1060 --- .../modules_app/system-commands/commands/grep.cfc | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/cfml/system/modules_app/system-commands/commands/grep.cfc b/src/cfml/system/modules_app/system-commands/commands/grep.cfc index effaf3217..67d5c3611 100644 --- a/src/cfml/system/modules_app/system-commands/commands/grep.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/grep.cfc @@ -22,21 +22,30 @@ component excludeFromHelp=true { /** * @input.hint The piped input to be checked. * @expression.hint A regular expression to match against each line of the input. Only matching lines will be output. + * @count.hint Return only a count of the matched rows **/ - function run( input='', expression='' ) { + function run( input='', expression='', boolean count=false ) { // Turn output into an array, breaking on carriage returns var content = listToArray( arguments.input, CR ); + var numMatches = 0; // Loop over content for( var line in content ) { // Does it match if( reFindNoCase( arguments.expression, line ) ) { - // print it out - print.line( line ); + if( count ) { + numMatches++; + } else { + print.line( line ); + } } } + + if( count ) { + print.line( numMatches ); + } } } From 7bcc403c4477abf7ed9f8dd9cf8df86c0e7e50a3 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 28 Oct 2019 11:24:59 -0500 Subject: [PATCH 021/102] COMMANDBOX-1060 --- src/cfml/system/modules_app/system-commands/commands/grep.cfc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfml/system/modules_app/system-commands/commands/grep.cfc b/src/cfml/system/modules_app/system-commands/commands/grep.cfc index 67d5c3611..5a39ce7be 100644 --- a/src/cfml/system/modules_app/system-commands/commands/grep.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/grep.cfc @@ -33,7 +33,7 @@ component excludeFromHelp=true { for( var line in content ) { // Does it match - if( reFindNoCase( arguments.expression, line ) ) { + if( arguments.expression == '' || reFindNoCase( arguments.expression, line ) ) { if( count ) { numMatches++; } else { From 6340f7ba389543bf38fefd77fb5653c465e1b1cd Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 29 Oct 2019 10:36:33 -0500 Subject: [PATCH 022/102] COMMANDBOX-1061 --- src/cfml/system/util/CommandDSL.cfc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cfml/system/util/CommandDSL.cfc b/src/cfml/system/util/CommandDSL.cfc index 70224b540..9c78573cd 100644 --- a/src/cfml/system/util/CommandDSL.cfc +++ b/src/cfml/system/util/CommandDSL.cfc @@ -98,7 +98,10 @@ component accessors=true { if( runCommand ) { processedParams.append( param ); } else if( getRawParams() ) { - processedParams.append( '"#param#"' ); + // The tokenizing process escapes any = signs in quotes as \= as a convenience so it doesn't look like a named param + // We're skipping the tokenzier since we're going to directly pass an array of tokes, but we still need to handle + // any = signs inside of quotes or the param processor will think it is a named parameter since we don't re-processes quote at that time + processedParams.append( '"#replace( param, "=", "\=", "all" )#"' ); } else { processedParams.append( '"#parser.escapeArg( param )#"' ); } From 17148ebb493710cddb51bcaccab2ae9c07099cfb Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 29 Oct 2019 10:42:45 -0500 Subject: [PATCH 023/102] COMMANDBOX-1061 --- src/cfml/system/util/CommandDSL.cfc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/cfml/system/util/CommandDSL.cfc b/src/cfml/system/util/CommandDSL.cfc index 9c78573cd..22225f21a 100644 --- a/src/cfml/system/util/CommandDSL.cfc +++ b/src/cfml/system/util/CommandDSL.cfc @@ -98,10 +98,13 @@ component accessors=true { if( runCommand ) { processedParams.append( param ); } else if( getRawParams() ) { - // The tokenizing process escapes any = signs in quotes as \= as a convenience so it doesn't look like a named param + // The tokenizing process escapes any unescaped = signs in quotes as \= as a convenience so it doesn't look like a named param // We're skipping the tokenzier since we're going to directly pass an array of tokes, but we still need to handle // any = signs inside of quotes or the param processor will think it is a named parameter since we don't re-processes quote at that time - processedParams.append( '"#replace( param, "=", "\=", "all" )#"' ); + param = replace( param, "\=", "__escaped_equals__", "all" ); + param = replace( param, "=", "\=", "all" ); + param = replace( param, "__escaped_equals__", "\=", "all" ); + processedParams.append( '"#param#"' ); } else { processedParams.append( '"#parser.escapeArg( param )#"' ); } From 7bbb6c0709b2e084eb286d7d3da2f5303c14fec4 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 29 Oct 2019 11:34:37 -0500 Subject: [PATCH 024/102] COMMANDBOX-1062 --- src/cfml/system/util/jline/CommandCompletor.cfc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/cfml/system/util/jline/CommandCompletor.cfc b/src/cfml/system/util/jline/CommandCompletor.cfc index 8d0878407..85bfee310 100644 --- a/src/cfml/system/util/jline/CommandCompletor.cfc +++ b/src/cfml/system/util/jline/CommandCompletor.cfc @@ -177,6 +177,10 @@ component singleton { if( !len( leftOver ) || lcase( flagParamName ).startsWith( lcase( leftOver ) ) ) { add( candidates, flagParamName, 'Flags', param.hint ?: '', true ); } + var flagParamName = '--no' & param.name; + if( !len( leftOver ) || lcase( flagParamName ).startsWith( lcase( leftOver ) ) ) { + add( candidates, flagParamName, 'Flags', param.hint ?: '', true ); + } } } // Does it exist yet? } // Loop over possible params @@ -248,6 +252,10 @@ component singleton { if( lcase( paramFlagname ).startsWith( lcase( partialMatch ) ) ) { add( candidates, paramFlagname, 'Flags', param.hint ?: '', true ); } + var paramFlagname = '--no' & param.name; + if( lcase( paramFlagname ).startsWith( lcase( partialMatch ) ) ) { + add( candidates, paramFlagname, 'Flags', param.hint ?: '', true ); + } } } @@ -285,6 +293,12 @@ component singleton { if( param.type == 'boolean' && !structKeyExists( passedParameters.flags, param.name ) && lcase( paramFlagname ).startsWith( lcase( partialMatch ) ) ) { add( candidates, paramFlagname, 'Flags', param.hint ?: '', true ); } + + // If this param is a boolean that isn't a flag yet, sugguest the --flag version + var paramFlagname = '--no' & param.name; + if( param.type == 'boolean' && !structKeyExists( passedParameters.flags, param.name ) && lcase( paramFlagname ).startsWith( lcase( partialMatch ) ) ) { + add( candidates, paramFlagname, 'Flags', param.hint ?: '', true ); + } } // Grab first param From 9204b9d718a9d34d488fe2435c6a32dd92a4b560 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 31 Oct 2019 17:28:52 -0500 Subject: [PATCH 025/102] COMMANDBOX-1063 --- build/build.properties | 3 ++- build/build.xml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/build/build.properties b/build/build.properties index 571a5b49d..bd22cac50 100644 --- a/build/build.properties +++ b/build/build.properties @@ -19,7 +19,8 @@ jre.adoptVesionr=openjdk11 jre.version=jdk-11.0.4+11 launch4j.version=3.12 runwar.version=3.8.1-SNAPSHOT -jline.version=3.10.0 +jline.version=3.12.0 +jansi.version=1.18 jgit.version=5.3.0.201903130848-r #build locations diff --git a/build/build.xml b/build/build.xml index 86404a002..74a45db26 100644 --- a/build/build.xml +++ b/build/build.xml @@ -1023,6 +1023,7 @@ External Dependencies: + From 7fcdfe63c3921219900095ee94721059948e4077 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 31 Oct 2019 17:37:17 -0500 Subject: [PATCH 026/102] COMMANDBOX-963 --- build/build.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/build.properties b/build/build.properties index bd22cac50..6c7b391f8 100644 --- a/build/build.properties +++ b/build/build.properties @@ -10,13 +10,13 @@ java.debug=true #dependencies dependencies.dir=${basedir}/lib -cfml.version=5.3.5.10-SNAPSHOT +cfml.version=5.3.4.59-SNAPSHOT cfml.loader.version=2.3.2 cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} lucee.config.version=5.2.4.37 jre.adoptVesionr=openjdk11 -jre.version=jdk-11.0.4+11 +jre.version=jdk-11.0.5+10 launch4j.version=3.12 runwar.version=3.8.1-SNAPSHOT jline.version=3.12.0 From 3b4667cb9772ac76b35f4da91d4ecc11411a4238 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 1 Nov 2019 12:47:51 -0500 Subject: [PATCH 027/102] Stupid Lucee string member functionsn don't work on boolean --- src/cfml/system/util/InteractiveJob.cfc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cfml/system/util/InteractiveJob.cfc b/src/cfml/system/util/InteractiveJob.cfc index 8e2058375..549b65726 100644 --- a/src/cfml/system/util/InteractiveJob.cfc +++ b/src/cfml/system/util/InteractiveJob.cfc @@ -87,7 +87,7 @@ component accessors=true singleton { getCurrentJob() .logLines.append( // Any log lines with a line break needs to become multuple lines - line + toString( line ) // Break multiple lines into array .listToArray( chr( 13 ) & chr( 10 ) ) // Break lines longer than the current terminal width into multiples @@ -287,7 +287,7 @@ component accessors=true singleton { // Add error message if it exists if( job.errorMessage.len() ) { - job.errorMessage.listToArray( chr( 13 ) & chr( 10 ) ).each( function( thisErrorLine) { + toString( job.errorMessage ).listToArray( chr( 13 ) & chr( 10 ) ).each( function( thisErrorLine) { lines.append( aStr.fromAnsi( print.redText( ' | > ' & thisErrorLine ) ) ); } ); } From 9bb8c33c7911fa4b8bffc35e1b7c4cade5ce0481 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 1 Nov 2019 21:09:04 -0500 Subject: [PATCH 028/102] Typo --- src/cfml/system/services/ServerService.cfc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index a0c53cd61..3ba7221e0 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -1229,7 +1229,7 @@ component accessors="true" singleton { if( !fileSystemUtil.isWindows() && background ) { // The shell script will take care of creating this file and emptying it every time var nohupLog = '#serverInfo.serverHomeDirectory#/nohup.log'; - // Pass log file to external process. This is no we can capture the output of the server process + // Pass log file to external process. This is so we can capture the output of the server process args.prepend( '#serverInfo.serverHomeDirectory#/nohup.log' ); // Use this intermediate shell script to start our server via nohup args.prepend( expandPath( '/server-commands/bin/server_spawner.sh' ) ); From bb177e2298bbdd9c597bd04dde28946caa9c60da Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 4 Nov 2019 20:05:31 -0600 Subject: [PATCH 029/102] COMMANDBOX-1065 --- .gitignore | 1 + build/build.properties | 5 +- build/build.xml | 32 +- src/java/luceecli/CLIContext.java | 412 ----------------------- src/java/luceecli/CLIMain.java | 252 -------------- src/java/luceecli/ServletConfigImpl.java | 69 ---- 6 files changed, 35 insertions(+), 736 deletions(-) delete mode 100644 src/java/luceecli/CLIContext.java delete mode 100644 src/java/luceecli/CLIMain.java delete mode 100644 src/java/luceecli/ServletConfigImpl.java diff --git a/.gitignore b/.gitignore index 3069c7b55..a66314d11 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ tests/work Icon? src/cfml/logs build/launch4j-* +build/runwar tests/testbox /mxunit /apidocs/CommandBox-CommandDocs/ diff --git a/build/build.properties b/build/build.properties index 6c7b391f8..3d368f94c 100644 --- a/build/build.properties +++ b/build/build.properties @@ -11,14 +11,15 @@ java.debug=true #dependencies dependencies.dir=${basedir}/lib cfml.version=5.3.4.59-SNAPSHOT -cfml.loader.version=2.3.2 +cfml.loader.version=2.3.3 cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} lucee.config.version=5.2.4.37 jre.adoptVesionr=openjdk11 jre.version=jdk-11.0.5+10 launch4j.version=3.12 -runwar.version=3.8.1-SNAPSHOT +runwar.version=4.0.0 +#runwar.version=3.8.1-SNAPSHOT jline.version=3.12.0 jansi.version=1.18 jgit.version=5.3.0.201903130848-r diff --git a/build/build.xml b/build/build.xml index 74a45db26..101194691 100644 --- a/build/build.xml +++ b/build/build.xml @@ -1035,7 +1035,6 @@ External Dependencies: - @@ -1052,9 +1051,40 @@ External Dependencies: + + + + + + + + + + + + + + Runwar Binary found! + + + runwar not found or snapshot build, getting artifact from ${ortus.repoURL} + + + + + + + + + + + + diff --git a/src/java/luceecli/CLIContext.java b/src/java/luceecli/CLIContext.java deleted file mode 100644 index b21247a1d..000000000 --- a/src/java/luceecli/CLIContext.java +++ /dev/null @@ -1,412 +0,0 @@ -/** - * Copyright (C) 2012 Ortus Solutions, Corp - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package luceecli; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Enumeration; -import java.util.EventListener; -import java.util.Map; -import java.util.Set; - -import javax.servlet.Filter; -import javax.servlet.FilterRegistration; -import javax.servlet.FilterRegistration.Dynamic; -import javax.servlet.RequestDispatcher; -import javax.servlet.Servlet; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.ServletRegistration; -import javax.servlet.SessionCookieConfig; -import javax.servlet.SessionTrackingMode; -import javax.servlet.descriptor.JspConfigDescriptor; - -import lucee.cli.util.EnumerationWrapper; - -@SuppressWarnings( "unchecked" ) -public class CLIContext implements ServletContext{ - private final Map< String, Object > attributes; - private final int majorVersion; - private final int minorVersion; - private final Map< String, String > parameters; - private final File root; - private final File webInf; - - public CLIContext( File root, File webInf, - Map< String, Object > attributes, Map< String, String > parameters, - int majorVersion, int minorVersion ){ - this.root = root; - this.webInf = webInf; - this.attributes = attributes; - this.parameters = parameters; - this.majorVersion = majorVersion; - this.minorVersion = minorVersion; - } - - @Override - public Dynamic addFilter( String arg0, Class< ? extends Filter > arg1 ){ - return null; - } - - @Override - public Dynamic addFilter( String arg0, Filter arg1 ){ - return null; - } - - @Override - public Dynamic addFilter( String arg0, String arg1 ){ - return null; - } - - @Override - public void addListener( Class< ? extends EventListener > arg0 ){ - } - - @Override - public void addListener( String arg0 ){ - } - - @Override - public < T extends EventListener >void addListener( T arg0 ){ - } - - @Override - public javax.servlet.ServletRegistration.Dynamic addServlet( String arg0, - Class< ? extends Servlet > arg1 ){ - return null; - } - - @Override - public javax.servlet.ServletRegistration.Dynamic addServlet( String arg0, - Servlet arg1 ){ - return null; - } - - @Override - public javax.servlet.ServletRegistration.Dynamic addServlet( String arg0, - String arg1 ){ - return null; - } - - @Override - public < T extends Filter >T createFilter( Class< T > arg0 ) - throws ServletException{ - return null; - } - - @Override - public < T extends EventListener >T createListener( Class< T > arg0 ) - throws ServletException{ - return null; - } - - @Override - public < T extends Servlet >T createServlet( Class< T > arg0 ) - throws ServletException{ - return null; - } - - @Override - public void declareRoles( String ... arg0 ){ - } - - /** - * @see javax.servlet.ServletContext#getAttribute(java.lang.String) - */ - @Override - public Object getAttribute( String key ){ - return this.attributes.get( key ); - } - - /** - * @see javax.servlet.ServletContext#getAttributeNames() - */ - @Override - public Enumeration< String > getAttributeNames(){ - return new EnumerationWrapper( this.attributes ); - } - - @Override - public ClassLoader getClassLoader(){ - return null; - } - - @Override - public ServletContext getContext( String key ){ - return this; - } - - @Override - public String getContextPath(){ - return null; - } - - @Override - public Set< SessionTrackingMode > getDefaultSessionTrackingModes(){ - return null; - } - - @Override - public int getEffectiveMajorVersion(){ - return 0; - } - - @Override - public int getEffectiveMinorVersion(){ - return 0; - } - - @Override - public Set< SessionTrackingMode > getEffectiveSessionTrackingModes(){ - return null; - } - - @Override - public FilterRegistration getFilterRegistration( String arg0 ){ - return null; - } - - @Override - public Map< String, ? extends FilterRegistration > getFilterRegistrations(){ - return null; - } - - /** - * @see javax.servlet.ServletContext#getInitParameter(java.lang.String) - */ - @Override - public String getInitParameter( String key ){ - return this.parameters.get( key ); - } - - /** - * @see javax.servlet.ServletContext#getInitParameterNames() - */ - @Override - public Enumeration< String > getInitParameterNames(){ - return new EnumerationWrapper( this.parameters ); - } - - @Override - public JspConfigDescriptor getJspConfigDescriptor(){ - return null; - } - - /** - * @see javax.servlet.ServletContext#getMajorVersion() - */ - @Override - public int getMajorVersion(){ - return this.majorVersion; - } - - /** - * @see javax.servlet.ServletContext#getMimeType(java.lang.String) - */ - @Override - public String getMimeType( String file ){ - throw notSupported( "getMimeType(String file)" ); - // TODO - // return ResourceUtil.getMymeType(config.getResource(file),null); - } - - /** - * @see javax.servlet.ServletContext#getMinorVersion() - */ - @Override - public int getMinorVersion(){ - return this.minorVersion; - } - - @Override - public RequestDispatcher getNamedDispatcher( String name ){ - throw notSupported( "getNamedDispatcher(String name)" ); - } - - public File getRealFile( String realpath ){ - if( realpath.startsWith( "/WEB-INF" ) ) { - return new File( this.webInf, realpath ); - } - File relPath = new File( this.root, realpath ); - if( relPath.exists() ) { - return relPath; - } - return new File( realpath ); - } - - /** - * @see javax.servlet.ServletContext#getRealPath(java.lang.String) - */ - @Override - public String getRealPath( String realpath ){ - return getRealFile( realpath ).getAbsolutePath(); - } - - @Override - public RequestDispatcher getRequestDispatcher( String path ){ - throw notSupported( "getNamedDispatcher(String name)" ); - } - - /** - * @see javax.servlet.ServletContext#getResource(java.lang.String) - */ - @Override - public URL getResource( String realpath ) throws MalformedURLException{ - File file = getRealFile( realpath ); - return file.toURI().toURL(); - } - - /** - * @see javax.servlet.ServletContext#getResourceAsStream(java.lang.String) - */ - @Override - public InputStream getResourceAsStream( String realpath ){ - try { - return new FileInputStream( getRealFile( realpath ) ); - } catch ( IOException e ) { - return null; - } - } - - @Override - public Set< String > getResourcePaths( String realpath ){ - throw notSupported( "getResourcePaths(String realpath)" ); - } - - public File getRoot(){ - return this.root; - } - - @Override - public String getServerInfo(){ - // deprecated - throw notSupported( "getServlet()" ); - } - - @Override - public Servlet getServlet( String arg0 ) throws ServletException{ - // deprecated - throw notSupported( "getServlet()" ); - } - - @Override - public String getServletContextName(){ - // can return null - return null; - } - - @Override - public Enumeration< String > getServletNames(){ - // deprecated - throw notSupported( "getServlet()" ); - } - - @Override - public ServletRegistration getServletRegistration( String arg0 ){ - return null; - } - - @Override - public Map< String, ? extends ServletRegistration > getServletRegistrations(){ - return null; - } - - @Override - public Enumeration< Servlet > getServlets(){ - // deprecated - throw notSupported( "getServlet()" ); - } - - @Override - public SessionCookieConfig getSessionCookieConfig(){ - return null; - } - - @Override - public String getVirtualServerName(){ - return null; - } - - /** - * @see javax.servlet.ServletContext#log(java.lang.Exception, - * java.lang.String) - */ - @Override - public void log( Exception e, String msg ){ - log( msg, e ); - } - - /** - * @see javax.servlet.ServletContext#log(java.lang.String) - */ - @Override - public void log( String msg ){ - log( msg, null ); - } - - /** - * @see javax.servlet.ServletContext#log(java.lang.String, - * java.lang.Throwable) - */ - @Override - public void log( String msg, Throwable t ){// TODO better - if( t == null ) { - System.out.println( msg ); - } else { - System.out.println( msg + ":\n" + t.getMessage() ); - // if(t==null)log.log(Log.LEVEL_INFO, "ServletContext", msg); - // else log.log(Log.LEVEL_ERROR, "ServletContext", - // msg+":\n"+ExceptionUtil.getStacktrace(t,false)); - } - } - - private RuntimeException notSupported( String method ){ - throw new RuntimeException( new ServletException( "method " + method - + " not supported" ) ); - } - - /** - * @see javax.servlet.ServletContext#removeAttribute(java.lang.String) - */ - @Override - public void removeAttribute( String key ){ - this.attributes.remove( key ); - } - - /** - * @see javax.servlet.ServletContext#setAttribute(java.lang.String, - * java.lang.Object) - */ - @Override - public void setAttribute( String key, Object value ){ - this.attributes.put( key, value ); - } - - @Override - public boolean setInitParameter( String arg0, String arg1 ){ - return false; - } - - @Override - public void setSessionTrackingModes( Set< SessionTrackingMode > arg0 ){ - } - -} diff --git a/src/java/luceecli/CLIMain.java b/src/java/luceecli/CLIMain.java deleted file mode 100644 index 72257bf5e..000000000 --- a/src/java/luceecli/CLIMain.java +++ /dev/null @@ -1,252 +0,0 @@ -/** - * Copyright (C) 2012 Ortus Solutions, Corp - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package luceecli; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.PrintStream; -import java.util.HashMap; -import java.util.Map; - -import javax.servlet.ServletException; -import javax.servlet.jsp.JspException; - -import lucee.loader.engine.CFMLEngine; -import lucee.loader.engine.CFMLEngineFactory; -import lucee.loader.util.Util; - -public class CLIMain{ - /** - * @param args - * @throws JspException - */ - public static void main( String[] args ) throws ServletException, - IOException, JspException{ - Map< String, String > config = toMap( args ); - Boolean debug = false; - - File currentDir = new File( CLIMain.class.getProtectionDomain() - .getCodeSource().getLocation().getPath() ).getParentFile(); - File libDir = new File( currentDir.getPath() ).getCanonicalFile(); - - // libs dir - String strLibs = config.get( "libs" ); - if( strLibs != null && strLibs.length() != 0 ) { - libDir = new File( strLibs ); - } - - // debug - String strDebug = config.get( "debug" ); - if( !Util.isEmpty( strDebug, true ) ) { - debug = true; - } - - if( debug ) { - System.out.println( "libDir dir" + libDir.getPath() ); - } - - // webroot - String strRoot = config.get( "webroot" ); - File root; - if( Util.isEmpty( strRoot, true ) ) { - // root=new File("./").getCanonicalFile(); - root = new File( "/" ).getCanonicalFile(); - } else { - root = new File( strRoot ); - } - config.put( "webroot", root.getPath() ); - // root.mkdirs(); - - String strServerroot = config.get( "server-config" ); - File serverRoot; - if( Util.isEmpty( strServerroot, true ) ) { - serverRoot = new File( libDir.getParentFile(), "server" ); - // serverRoot=libDir; - } else { - serverRoot = new File( strServerroot ); - } - config.put( "server-config", serverRoot.getAbsolutePath() ); - // serverRoot.mkdirs(); - - String strWebroot = config.get( "web-config" ); - File webConfig; - if( Util.isEmpty( strWebroot, true ) ) { - webConfig = new File( libDir.getParentFile(), "server/lucee-web" ); - } else { - webConfig = new File( strWebroot ); - } - config.put( "web-config", webConfig.getAbsolutePath() ); - // webRoot.mkdirs(); - - // if no uri arg, use first non -dashed arg - String strUri = config.get( "uri" ); - if( strUri == null || strUri.length() == 0 ) { - String raw; - if( args != null ) { - for( String arg : args) { - raw = arg.trim(); - if( raw.length() == 0 ) { - continue; - } - if( !raw.startsWith( "-" ) ) { - File rawFile = new File( raw ).getCanonicalFile(); - // otherwise it begins a command line - if( rawFile.exists() ) { - raw = rawFile.getPath(); - config.put( "uri", raw ); - } - break; - } - } - } - } - // fix for windows. remove drive letter. TODO: find out why this is - // needed - config.put( "uri", new File( config.get( "uri" ) ).toURI().toURL() - .toExternalForm().replaceAll( "file:/(\\w:)", "file://$1" ) ); - // hack to prevent . being picked up as the system path (jacob.x.dll) - if( System.getProperty( "java.library.path" ) == null ) { - System.setProperty( "java.library.path", libDir.getPath() ); - } else { - System.setProperty( "java.library.path", libDir.getPath() + ":" - + System.getProperty( "java.library.path" ) ); - } - if( debug ) { - System.out.println( "Config:" + config ); - } - // servletNane - String servletName = config.get( "servlet-name" ); - if( Util.isEmpty( servletName, true ) ) { - servletName = "CFMLServlet"; - } - - Map< String, Object > attributes = new HashMap< String, Object >(); - Map< String, String > initParameters = new HashMap< String, String >(); - initParameters.put( "lucee-server-directory", - serverRoot.getAbsolutePath() ); - initParameters.put( "configuration", webConfig.getAbsolutePath() ); - - CLIContext servletContext = new CLIContext( root, webConfig, - attributes, initParameters, 1, 0 ); - ServletConfigImpl servletConfig = new ServletConfigImpl( - servletContext, servletName ); - PrintStream printStream = new PrintStream( new ByteArrayOutputStream() ); - PrintStream origOut = System.out; - // hide engine startup stuff - if( !debug ) { - System.setOut( printStream ); - } - CFMLEngine engine = null; - try { - engine = CFMLEngineFactory.getInstance( servletConfig ); - } catch ( Exception e ) { - e.printStackTrace(); - } finally { - System.setOut( origOut ); - } - printStream.close(); - - engine.cli( config, servletConfig ); - - } - - // java lucee-cli.jar -config=.../lucee-web.xml.cfm - // -uri=/susi/index.cfm?test=1 -form=name=susi -cgi=user_agent=urs - // -output=.../test.txt ... - /** - * Config - * - * webroot - webroot directory servlet-name - name of the servlet - * (default:CFMLServlet) server-name - server name (default:localhost) uri - - * host/scriptname/query cookie - cookies (same pattern as query string) - */ - /** - * @param uri - * @param debug - * @param args - * @throws JspException - */ - public static void run( File webroot, File configServerDir, - File configWebDir, String uri, boolean debug ) - throws ServletException, IOException, JspException{ - Map< String, String > config = new HashMap< String, String >(); - config.put( "webroot", webroot.getPath() ); - config.put( "server-config", configServerDir.getAbsolutePath() ); - config.put( "web-config", configWebDir.getAbsolutePath() ); - config.put( "uri", new File( uri ).toURI().toURL().toExternalForm() - .replaceAll( "file:/(\\w:)", "file://$1" ) ); - String servletName = config.get( "servlet-name" ); - if( Util.isEmpty( servletName, true ) ) { - servletName = "CFMLServlet"; - } - - Map< String, Object > attributes = new HashMap< String, Object >(); - Map< String, String > initParameters = new HashMap< String, String >(); - initParameters.put( "lucee-server-directory", - configServerDir.getAbsolutePath() ); - initParameters.put( "configuration", configWebDir.getAbsolutePath() ); - - CLIContext servletContext = new CLIContext( webroot, configWebDir, - attributes, initParameters, 1, 0 ); - ServletConfigImpl servletConfig = new ServletConfigImpl( - servletContext, servletName ); - PrintStream printStream = new PrintStream( new ByteArrayOutputStream() ); - PrintStream origOut = System.out; - // hide engine startup stuff - if( !debug ) { - System.setOut( printStream ); - } - CFMLEngine engine = null; - try { - engine = CFMLEngineFactory.getInstance( servletConfig ); - } catch ( Exception e ) { - e.printStackTrace(); - } finally { - System.setOut( origOut ); - } - printStream.close(); - engine.cli( config, servletConfig ); - } - - private static Map< String, String > toMap( String[] args ){ - int index; - Map< String, String > config = new HashMap< String, String >(); - String raw, key, value; - if( args != null ) { - for( String arg : args) { - raw = arg.trim(); - if( Util.isEmpty( raw, true ) ) { - continue; - } - if( raw.startsWith( "-" ) ) { - raw = raw.substring( 1 ).trim(); - } - index = raw.indexOf( '=' ); - if( index == -1 ) { - key = raw; - value = ""; - } else { - key = raw.substring( 0, index ).trim(); - value = raw.substring( index + 1 ).trim(); - } - config.put( key.toLowerCase(), value ); - } - } - return config; - } -} diff --git a/src/java/luceecli/ServletConfigImpl.java b/src/java/luceecli/ServletConfigImpl.java deleted file mode 100644 index ada8b3a35..000000000 --- a/src/java/luceecli/ServletConfigImpl.java +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright (C) 2012 Ortus Solutions, Corp - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package luceecli; - -import java.util.Enumeration; - -import javax.servlet.ServletConfig; -import javax.servlet.ServletContext; - -public class ServletConfigImpl implements ServletConfig{ - - private final ServletContext context; - private final String servletName; - - /** - * Constructor of the class - * - * @param parameters - * @param attrs - * @param servletName - */ - public ServletConfigImpl( CLIContext context, String servletName ){ - this.servletName = servletName; - this.context = context; - } - - /** - * @see javax.servlet.ServletConfig#getInitParameter(java.lang.String) - */ - @Override - public String getInitParameter( String key ){ - return this.context.getInitParameter( key ); - } - - /** - * @see javax.servlet.ServletConfig#getInitParameterNames() - */ - @Override - public Enumeration< String > getInitParameterNames(){ - return this.context.getInitParameterNames(); - } - - @Override - public ServletContext getServletContext(){ - return this.context; - } - - /** - * @see javax.servlet.ServletConfig#getServletName() - */ - @Override - public String getServletName(){ - return this.servletName; - } -} \ No newline at end of file From dd3ba567a2c9778960886bca31995e292876bc34 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 4 Nov 2019 23:36:01 -0600 Subject: [PATCH 030/102] COMMANDBOX-1066 --- build/build.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/build.properties b/build/build.properties index 3d368f94c..4898e74f9 100644 --- a/build/build.properties +++ b/build/build.properties @@ -11,7 +11,7 @@ java.debug=true #dependencies dependencies.dir=${basedir}/lib cfml.version=5.3.4.59-SNAPSHOT -cfml.loader.version=2.3.3 +cfml.loader.version=2.3.4 cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} lucee.config.version=5.2.4.37 @@ -22,7 +22,7 @@ runwar.version=4.0.0 #runwar.version=3.8.1-SNAPSHOT jline.version=3.12.0 jansi.version=1.18 -jgit.version=5.3.0.201903130848-r +jgit.version=5.5.1.201910021850-r #build locations build.type=localdev From 44d776f8622c76bc4a0f42c4a5764b94bc3516ec Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 7 Nov 2019 11:14:37 -0600 Subject: [PATCH 031/102] Runwar versions have been fixed on S3 --- build/build.properties | 2 +- build/build.xml | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/build/build.properties b/build/build.properties index 4898e74f9..2bb3d7b7e 100644 --- a/build/build.properties +++ b/build/build.properties @@ -18,7 +18,7 @@ lucee.config.version=5.2.4.37 jre.adoptVesionr=openjdk11 jre.version=jdk-11.0.5+10 launch4j.version=3.12 -runwar.version=4.0.0 +runwar.version=4.0.0-SNAPSHOT #runwar.version=3.8.1-SNAPSHOT jline.version=3.12.0 jansi.version=1.18 diff --git a/build/build.xml b/build/build.xml index 101194691..5a118cfa1 100644 --- a/build/build.xml +++ b/build/build.xml @@ -1079,10 +1079,7 @@ External Dependencies: - - - - + From f466a3ab24cd60e501b50d79f23e50f449f8ced9 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 8 Nov 2019 22:08:21 -0600 Subject: [PATCH 032/102] COMMANDBOX-1067 --- src/cfml/system/services/ServerService.cfc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 3ba7221e0..44b015fe1 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -142,6 +142,7 @@ component accessors="true" singleton { 'accessLogEnable' : d.web.accessLogEnable ?: false, 'GZIPEnable' : d.web.GZIPEnable ?: true, 'welcomeFiles' : d.web.welcomeFiles ?: '', + 'maxRequests' : d.web.maxRequests ?: '', 'HTTP' : { 'port' : d.web.http.port ?: 0, 'enable' : d.web.http.enable ?: true @@ -591,6 +592,7 @@ component accessors="true" singleton { serverInfo.basicAuthEnable = serverJSON.web.basicAuth.enable ?: defaults.web.basicAuth.enable; serverInfo.basicAuthUsers = serverJSON.web.basicAuth.users ?: defaults.web.basicAuth.users; serverInfo.welcomeFiles = serverProps.welcomeFiles ?: serverJSON.web.welcomeFiles ?: defaults.web.welcomeFiles; + serverInfo.maxRequests = serverJSON.web.maxRequests ?: defaults.web.maxRequests; serverInfo.trayEnable = serverJSON.trayEnable ?: defaults.trayEnable; @@ -1109,6 +1111,9 @@ component accessors="true" singleton { if( len( serverInfo.welcomeFiles ) ) { args.append( '--welcome-files' ).append( serverInfo.welcomeFiles ); } + if( len( serverInfo.maxRequests ) ) { + args.append( '--worker-threads' ).append( serverInfo.maxRequests ); + } if( len( CLIAliases ) ) { args.append( '--dirs' ).append( CLIAliases ); } @@ -2049,6 +2054,7 @@ component accessors="true" singleton { 'openBrowserURL' : '', 'customServerFolder': '', 'welcomeFiles' : '', + 'maxRequests' : '', 'exitCode' : 0 }; } From af21eeae762d648d00c7bb07897b8162207420d8 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 8 Nov 2019 22:25:11 -0600 Subject: [PATCH 033/102] COMMANDBOX-1028 --- src/cfml/system/services/ServerService.cfc | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 44b015fe1..8903bbc05 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -1070,8 +1070,6 @@ component accessors="true" singleton { if( len( serverInfo.restMappings ) ) { args.append( '--servlet-rest-mappings' ).append( serverInfo.restMappings ); - } else { - args.append( '--servlet-rest-mappings' ).append( '__DISABLED__' ); } if( serverInfo.trace ) { From 3de93b5c4ef9823f5beea1e4c81a9ab8f79ca216 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 8 Nov 2019 23:07:15 -0600 Subject: [PATCH 034/102] COMMANDBOX-992 --- src/java/cliloader/LoaderCLIMain.java | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/java/cliloader/LoaderCLIMain.java b/src/java/cliloader/LoaderCLIMain.java index 225df376f..4b7b8743a 100644 --- a/src/java/cliloader/LoaderCLIMain.java +++ b/src/java/cliloader/LoaderCLIMain.java @@ -188,13 +188,17 @@ public static void execute( ArrayList< String > cliArguments, InputStream inputS } // Check for "box foo.cfm" or "box foo.cfm param1 ..." // This is mostly just for backwards compat and to enforce consistency. - if( cliArguments.size() > 0 - && cliArguments.get( 0 ).toLowerCase().endsWith( ".cfm" ) - && new File( cliArguments.get( 0 ) ).exists() ) { + + File arg1File = new File( cliArguments.get( 0 ) ); + + if( cliArguments.size() > 0 + && arg1File.exists() + && ( cliArguments.get( 0 ).toLowerCase().endsWith( ".cfm" ) + || isShebang( arg1File.getCanonicalPath() ) ) ) { log.debug( "Funneling: " + cliArguments.get( 0 ) + " through execute command." ); - String CFMLFile = new File( cliArguments.get( 0 ) ).getCanonicalPath(); + String CFMLFile = arg1File.getCanonicalPath(); // handle bash script String CFMLFile2 = removeBinBash( CFMLFile ); @@ -207,7 +211,7 @@ && new File( cliArguments.get( 0 ) ).exists() ) { log.debug( "Executing: " + uri ); } else if( cliArguments.size() > 0 - && new File( cliArguments.get( 0 ) ).isFile() ) { + && arg1File.isFile() ) { String filename = cliArguments.get( 0 ).toLowerCase(); // This will force the shell to run the recipe command @@ -776,6 +780,16 @@ private static Properties mergeProperties( Properties source, return merged; } + private static Boolean isShebang( String uri ) throws IOException{ + FileReader namereader = new FileReader( new File( uri ) ); + BufferedReader in = new BufferedReader( namereader ); + String line = in.readLine(); + if( line != null && line.startsWith( "#!" ) ) { + return true; + } + return false; + } + private static String removeBinBash( String uri ) throws IOException{ FileReader namereader = new FileReader( new File( uri ) ); BufferedReader in = new BufferedReader( namereader ); From cba1ec9725ec25b55d9edc4bf5f38892e28f1b30 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 8 Nov 2019 23:13:07 -0600 Subject: [PATCH 035/102] COMMANDBOX-992 --- build/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.properties b/build/build.properties index 2bb3d7b7e..ae6d3a4e3 100644 --- a/build/build.properties +++ b/build/build.properties @@ -11,7 +11,7 @@ java.debug=true #dependencies dependencies.dir=${basedir}/lib cfml.version=5.3.4.59-SNAPSHOT -cfml.loader.version=2.3.4 +cfml.loader.version=2.3.5 cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} lucee.config.version=5.2.4.37 From a50ba81bf4cf251d6d17b96adb22b4a1295f1161 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Sat, 9 Nov 2019 00:15:43 -0600 Subject: [PATCH 036/102] Can't read if no args --- src/java/cliloader/LoaderCLIMain.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/java/cliloader/LoaderCLIMain.java b/src/java/cliloader/LoaderCLIMain.java index 4b7b8743a..9ea016bfd 100644 --- a/src/java/cliloader/LoaderCLIMain.java +++ b/src/java/cliloader/LoaderCLIMain.java @@ -188,17 +188,14 @@ public static void execute( ArrayList< String > cliArguments, InputStream inputS } // Check for "box foo.cfm" or "box foo.cfm param1 ..." // This is mostly just for backwards compat and to enforce consistency. - - File arg1File = new File( cliArguments.get( 0 ) ); - if( cliArguments.size() > 0 - && arg1File.exists() + && new File( cliArguments.get( 0 ) ).exists() && ( cliArguments.get( 0 ).toLowerCase().endsWith( ".cfm" ) - || isShebang( arg1File.getCanonicalPath() ) ) ) { + || isShebang( new File( cliArguments.get( 0 ) ).getCanonicalPath() ) ) ) { log.debug( "Funneling: " + cliArguments.get( 0 ) + " through execute command." ); - String CFMLFile = arg1File.getCanonicalPath(); + String CFMLFile = new File( cliArguments.get( 0 ) ).getCanonicalPath(); // handle bash script String CFMLFile2 = removeBinBash( CFMLFile ); @@ -211,7 +208,7 @@ public static void execute( ArrayList< String > cliArguments, InputStream inputS log.debug( "Executing: " + uri ); } else if( cliArguments.size() > 0 - && arg1File.isFile() ) { + && new File( cliArguments.get( 0 ) ).isFile() ) { String filename = cliArguments.get( 0 ).toLowerCase(); // This will force the shell to run the recipe command From 13bc4aa9aab4320cd2c42de3335201ecfaea9b5e Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Sat, 9 Nov 2019 00:26:40 -0600 Subject: [PATCH 037/102] Check for isFile() --- src/java/cliloader/LoaderCLIMain.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/java/cliloader/LoaderCLIMain.java b/src/java/cliloader/LoaderCLIMain.java index 9ea016bfd..0bb13a77b 100644 --- a/src/java/cliloader/LoaderCLIMain.java +++ b/src/java/cliloader/LoaderCLIMain.java @@ -190,6 +190,7 @@ public static void execute( ArrayList< String > cliArguments, InputStream inputS // This is mostly just for backwards compat and to enforce consistency. if( cliArguments.size() > 0 && new File( cliArguments.get( 0 ) ).exists() + && new File( cliArguments.get( 0 ) ).isFile() && ( cliArguments.get( 0 ).toLowerCase().endsWith( ".cfm" ) || isShebang( new File( cliArguments.get( 0 ) ).getCanonicalPath() ) ) ) { From 2886238c7eca085fc79abd784e46b643e990f9a6 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Sat, 9 Nov 2019 00:29:11 -0600 Subject: [PATCH 038/102] COMMANDBOX-975 --- src/cfml/system/Shell.cfc | 13 +++++++++++++ src/cfml/system/util/ReaderFactory.cfc | 1 + 2 files changed, 14 insertions(+) diff --git a/src/cfml/system/Shell.cfc b/src/cfml/system/Shell.cfc index b86ad27f1..ed4ede90b 100644 --- a/src/cfml/system/Shell.cfc +++ b/src/cfml/system/Shell.cfc @@ -247,6 +247,11 @@ component accessors="true" singleton { enableHistory( false ); } + var terminal = getReader().getTerminal(); + if( terminal.paused() ) { + terminal.resume(); + } + // read reponse while masking input var input = variables.reader.readLine( // Prompt for the user @@ -316,6 +321,9 @@ component accessors="true" singleton { } var terminal = getReader().getTerminal(); + if( terminal.paused() ) { + terminal.resume(); + } var keys = createObject( 'java', 'org.jline.keymap.KeyMap' ); var capability = createObject( 'java', 'org.jline.utils.InfoCmp$Capability' ); @@ -518,6 +526,11 @@ component accessors="true" singleton { interceptData.prompt = ''; } + var terminal = getReader().getTerminal(); + if( terminal.paused() ) { + terminal.resume(); + } + // Shell stops on this line while waiting for user input if( arguments.silent ) { line = variables.reader.readLine( interceptData.prompt, javacast( "char", ' ' ) ); diff --git a/src/cfml/system/util/ReaderFactory.cfc b/src/cfml/system/util/ReaderFactory.cfc index 15a2a6d31..32a23912b 100644 --- a/src/cfml/system/util/ReaderFactory.cfc +++ b/src/cfml/system/util/ReaderFactory.cfc @@ -83,6 +83,7 @@ component singleton{ .signalHandler( jSignalHandler ) // This hides the warning when JLine defaults to a dumb terminal on CI builds .dumb( true ) + //.paused( true ) .build(); // Build our reader instance From e977d17208d0223dcc1d5c5e71b975a251b0093b Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Sat, 9 Nov 2019 14:01:45 -0600 Subject: [PATCH 039/102] COMMANDBOX-975 --- src/cfml/system/Shell.cfc | 2 +- .../system/modules_app/system-commands/commands/tail.cfc | 6 ++++++ src/cfml/system/services/ServerService.cfc | 7 ++++++- src/cfml/system/util/ReaderFactory.cfc | 2 +- src/cfml/system/util/Watcher.cfc | 7 +++++++ 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/cfml/system/Shell.cfc b/src/cfml/system/Shell.cfc index ed4ede90b..c4c35811d 100644 --- a/src/cfml/system/Shell.cfc +++ b/src/cfml/system/Shell.cfc @@ -323,7 +323,7 @@ component accessors="true" singleton { var terminal = getReader().getTerminal(); if( terminal.paused() ) { terminal.resume(); - } + } var keys = createObject( 'java', 'org.jline.keymap.KeyMap' ); var capability = createObject( 'java', 'org.jline.utils.InfoCmp$Capability' ); diff --git a/src/cfml/system/modules_app/system-commands/commands/tail.cfc b/src/cfml/system/modules_app/system-commands/commands/tail.cfc index de1898cb6..384ca5d4f 100644 --- a/src/cfml/system/modules_app/system-commands/commands/tail.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/tail.cfc @@ -169,6 +169,12 @@ } // End thread + // Need to start reading the input stream or we can't detect Ctrl-C on Windows + var terminal = shell.getReader().getTerminal(); + if( terminal.paused() ) { + terminal.resume(); + } + while( true ) { // Detect user pressing Ctrl-C // Any other characters captured will be ignored diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 8903bbc05..c292cf25b 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -1368,11 +1368,16 @@ component accessors="true" singleton { if( !background ) { try { + // Need to start reading the input stream or we can't detect Ctrl-C on Windows + var terminal = shell.getReader().getTerminal(); + if( terminal.paused() ) { + terminal.resume(); + } variables.waitingOnConsoleStart = true; while( true ) { // For dumb terminals, just sit and wait to be interrupted // Trying to read from a dumb terminal will throw "The handle is invalid" errors - if( shell.getReader().getTerminal().getClass().getName() contains 'dumb' ) { + if( terminal.getClass().getName() contains 'dumb' ) { sleep( 500 ); } else { // Detect user pressing Ctrl-C diff --git a/src/cfml/system/util/ReaderFactory.cfc b/src/cfml/system/util/ReaderFactory.cfc index 32a23912b..b4d615d13 100644 --- a/src/cfml/system/util/ReaderFactory.cfc +++ b/src/cfml/system/util/ReaderFactory.cfc @@ -83,7 +83,7 @@ component singleton{ .signalHandler( jSignalHandler ) // This hides the warning when JLine defaults to a dumb terminal on CI builds .dumb( true ) - //.paused( true ) + .paused( true ) .build(); // Build our reader instance diff --git a/src/cfml/system/util/Watcher.cfc b/src/cfml/system/util/Watcher.cfc index 47f715ef4..ca3600247 100644 --- a/src/cfml/system/util/Watcher.cfc +++ b/src/cfml/system/util/Watcher.cfc @@ -148,6 +148,13 @@ component accessors=true { } // end thread while( true ){ + + // Need to start reading the input stream or we can't detect Ctrl-C on Windows + var terminal = shell.getReader().getTerminal(); + if( terminal.paused() ) { + terminal.resume(); + } + // Detect user pressing Ctrl-C // Any other characters captured will be ignored var line = shell.getReader().readLine(); From 86b1f171896c64e177fb8d31994992f2fa2db503 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Sat, 9 Nov 2019 14:02:10 -0600 Subject: [PATCH 040/102] COMMANDBOX-1063 --- build/build.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/build.properties b/build/build.properties index ae6d3a4e3..f2bde25d9 100644 --- a/build/build.properties +++ b/build/build.properties @@ -11,7 +11,7 @@ java.debug=true #dependencies dependencies.dir=${basedir}/lib cfml.version=5.3.4.59-SNAPSHOT -cfml.loader.version=2.3.5 +cfml.loader.version=2.3.6 cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} lucee.config.version=5.2.4.37 @@ -20,7 +20,7 @@ jre.version=jdk-11.0.5+10 launch4j.version=3.12 runwar.version=4.0.0-SNAPSHOT #runwar.version=3.8.1-SNAPSHOT -jline.version=3.12.0 +jline.version=3.13.0 jansi.version=1.18 jgit.version=5.5.1.201910021850-r From 21c60a553ae4da9ecf9adba803bba6100811bc52 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 14 Nov 2019 13:49:53 -0600 Subject: [PATCH 041/102] COMMANDBOX-1068 --- src/cfml/system/services/ServerService.cfc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index c292cf25b..842f4146d 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -243,6 +243,9 @@ component accessors="true" singleton { if( !isNull( serverProps.SSLKeyFile ) ) { serverProps.SSLKeyFile = fileSystemUtil.resolvePath( serverProps.SSLKeyFile ); } + if( !isNull( serverProps.javaHomeDirectory ) ) { + serverProps.javaHomeDirectory = fileSystemUtil.resolvePath( serverProps.javaHomeDirectory ); + } if( structKeyExists( serverProps, 'trace' ) && serverProps.trace ) { serverProps.debug = true; @@ -633,13 +636,13 @@ component accessors="true" singleton { serverInfo.javaVersion = serverProps.javaVersion; // Then server.json java home dir } else if( isDefined( 'serverJSON.JVM.javaHome' ) ) { - serverInfo.javaHome = serverJSON.JVM.javaHome; + serverInfo.javaHome = fileSystemUtil.resolvePath( serverJSON.JVM.javaHome, defaultServerConfigFileDirectory ); // Then server.json java version } else if( isDefined( 'serverJSON.JVM.javaVersion' ) ) { serverInfo.javaVersion = serverJSON.JVM.javaVersion; // Then server defaults java home dir } else if( defaults.JVM.javaHome.len() ) { - serverInfo.javaHome = defaults.JVM.javaHome; + serverInfo.javaHome = fileSystemUtil.resolvePath( defaults.JVM.javaHome, defaultwebroot ); // Then server defaults java versiom } else if( defaults.JVM.javaVersion.len() ) { serverInfo.javaVersion = defaults.JVM.javaVersion; From 785d072ee793d36240ca43f606dfb3e38c46205b Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 2 Dec 2019 20:01:53 -0600 Subject: [PATCH 042/102] COMMANDBOX-1074 --- src/cfml/system/Bootstrap.cfm | 13 ++++- src/cfml/system/Shell.cfc | 2 +- src/cfml/system/services/CommandService.cfc | 9 +++- src/cfml/system/services/FRTransService.cfc | 57 +++++++++++++++++++++ src/java/cliloader/LoaderCLIMain.java | 19 ++++++- 5 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 src/cfml/system/services/FRTransService.cfc diff --git a/src/cfml/system/Bootstrap.cfm b/src/cfml/system/Bootstrap.cfm index 4e348a4da..3e5d3927d 100644 --- a/src/cfml/system/Bootstrap.cfm +++ b/src/cfml/system/Bootstrap.cfm @@ -11,7 +11,6 @@ I am a CFM because the CLI seems to need a .cfm file to call This file will stay running the entire time the shell is open ---> - @@ -29,6 +28,12 @@ This file will stay running the entire time the shell is open applicationTimeout = "#createTimeSpan( 999999, 0, 0, 0 )#" mappings="#mappings#"> + + FRTransService = new commandbox.system.services.FRTransService(); + FRTransaction = FRTransService.startTransaction( 'CLI CF Startup', 'CF Code from start of CFM bootstrap until ready to process' ); + + + Generating ORM Virtual Service (#arguments.singularName#)' ); command( "coldbox create orm-virtual-service" ) .params( - entityName = arguments.singularName, - directory = arguments.modelsDirectory + entityName = arguments.singularName, + directory = arguments.modelsDirectory, + testsDirectory = arguments.specsDirectory & '/unit' ) .run(); @@ -200,7 +203,8 @@ component { name = ucFirst( arguments.singularName ), description = "I model a #arguments.singularName#", properties = arguments.properties, - directory = arguments.modelsDirectory + directory = arguments.modelsDirectory, + testsDirectory = arguments.specsDirectory & '/unit' ) .run(); @@ -208,11 +212,12 @@ component { print.blueLine( '--> Generating resource service (#arguments.resource#Service)' ); command( "coldbox create model" ) .params( - name = ucFirst( arguments.resource ) & "Service", - persistence = "singleton", - description = "I manage #arguments.singularName#", - methods = "save,delete,list,get", - directory = arguments.modelsDirectory + name = ucFirst( arguments.resource ) & "Service", + persistence = "singleton", + description = "I manage #arguments.singularName#", + methods = "save,delete,list,get", + directory = arguments.modelsDirectory, + testsDirectory = arguments.specsDirectory & '/unit' ) .run(); } From 419a5bdaee8eaff5702220a29a3b169e5a83170c Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 26 Dec 2019 19:47:02 -0600 Subject: [PATCH 055/102] COMMANDBOX-1007 --- src/cfml/system/services/ServerService.cfc | 28 ++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 842f4146d..70f42a3ca 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -185,7 +185,11 @@ component accessors="true" singleton { 'sessionCookieHTTPOnly' : d.app.sessionCookieHTTPOnly ?: false }, 'runwar' : { - 'args' : d.runwar.args ?: '' + 'args' : d.runwar.args ?: '', + // Duplicate so onServerStart interceptors don't actually change config settings via reference. + 'XNIOOptions' : duplicate( d.runwar.XNIOOptions ?: {} ), + // Duplicate so onServerStart interceptors don't actually change config settings via reference. + 'undertowOptions' : duplicate( d.runwar.undertowOptions ?: {} ) } }; } @@ -708,7 +712,13 @@ component accessors="true" singleton { // Global defauls are always added on top of whatever is specified by the user or server.json serverInfo.runwarArgs = ( serverProps.runwarArgs ?: serverJSON.runwar.args ?: '' ) & ' ' & defaults.runwar.args; - + + // Global defauls are always added on top of whatever is specified by the user or server.json + serverInfo.runwarXNIOOptions = ( serverJSON.runwar.XNIOOptions ?: {} ).append( defaults.runwar.XNIOOptions, true ); + + // Global defauls are always added on top of whatever is specified by the user or server.json + serverInfo.runwarUndertowOptions = ( serverJSON.runwar.UndertowOptions ?: {} ).append( defaults.runwar.UndertowOptions, true ); + // Server startup timeout serverInfo.startTimeout = serverProps.startTimeout ?: serverJSON.startTimeout ?: defaults.startTimeout; @@ -1065,7 +1075,15 @@ component accessors="true" singleton { .append( '--cookie-secure' ).append( serverInfo.sessionCookieSecure ) .append( '--cookie-httponly' ).append( serverInfo.sessionCookieHTTPOnly ) .append( serverInfo.runwarArgs.listToArray( ' ' ), true ); - + + if( serverInfo.runwarXNIOOptions.count() ) { + args.append( '--xnio-options=' & serverInfo.runwarXNIOOptions.reduce( ( opts='', k, v ) => opts.listAppend( k & '=' & v ) ) ); + } + + if( serverInfo.runwarUndertowOptions.count() ) { + args.append( '--undertow-options=' & serverInfo.runwarUndertowOptions.reduce( ( opts='', k, v ) => opts.listAppend( k & '=' & v ) ) ); + } + if( serverInfo.debug ) { // Debug is getting turned on any time I include the --debug flag regardless of whether it's true or false. args.append( '--debug' ).append( serverInfo.debug ); @@ -1247,7 +1265,7 @@ component accessors="true" singleton { var cleanedArgs = cr & ' ' & trim( reReplaceNoCase( args.toList( ' ' ), ' (-|"-)', cr & ' \1', 'all' ) ); job.addLog("Server start command: #cleanedargs#"); } - + processBuilder.init( args ); // incorporate CommandBox environment variables into the process's env @@ -2040,6 +2058,8 @@ component accessors="true" singleton { 'directoryBrowsing' : false, 'JVMargs' : "", 'runwarArgs' : "", + 'runwarXNIOOptions' : {}, + 'runwarUndertowOptions' : {}, 'cfengine' : "", 'restMappings' : "", 'sessionCookieSecure' : false, From 94bb08499aba0056b7d5597f628b54292c65de87 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 27 Dec 2019 16:11:30 -0600 Subject: [PATCH 056/102] COMMANDBOX-1079 --- .../system/modules_app/system-commands/commands/forEach.cfc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cfml/system/modules_app/system-commands/commands/forEach.cfc b/src/cfml/system/modules_app/system-commands/commands/forEach.cfc index 4e6ddbd5e..3ee29033a 100644 --- a/src/cfml/system/modules_app/system-commands/commands/forEach.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/forEach.cfc @@ -114,6 +114,8 @@ component { } else { rethrow; } + } finally { + checkinterrupted(); } } From 1609a44837bcda04fddfe26153121dd902796f5b Mon Sep 17 00:00:00 2001 From: John Berquist Date: Fri, 10 Jan 2020 16:31:12 -0800 Subject: [PATCH 057/102] Add `--command` flag to server start (#211) When `--command` is passed to `server start`, return the server start command args without actually executing it, so that it can be executed in a shell in the foreground. --- src/cfml/system/services/ServerService.cfc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 70f42a3ca..abb9010d5 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -337,7 +337,7 @@ component accessors="true" singleton { // Save hand-entered properties in our server.json for next time for( var prop in serverProps ) { // Ignore null props or ones that shouldn't be saved - if( isNull( serverProps[ prop ] ) || listFindNoCase( 'saveSettings,serverConfigFile,debug,force,console,trace', prop ) ) { + if( isNull( serverProps[ prop ] ) || listFindNoCase( 'saveSettings,serverConfigFile,debug,force,console,trace,command', prop ) ) { continue; } var configPath = replace( fileSystemUtil.resolvePath( defaultServerConfigFileDirectory ), '\', '/', 'all' ); @@ -1266,6 +1266,11 @@ component accessors="true" singleton { job.addLog("Server start command: #cleanedargs#"); } + if( serverProps.command ?: false ) { + job.complete( serverInfo.debug ); + return args.map( ( arg ) => '"#arg#"' ).toList( ' ' ); + } + processBuilder.init( args ); // incorporate CommandBox environment variables into the process's env From f83e9f16b8426489bbd12dd8f29d81d48b762182 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 10 Jan 2020 18:50:37 -0600 Subject: [PATCH 058/102] Add command param to server start --- .../modules_app/server-commands/commands/server/start.cfc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cfml/system/modules_app/server-commands/commands/server/start.cfc b/src/cfml/system/modules_app/server-commands/commands/server/start.cfc index cb9558c69..29c1b8abb 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/start.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/start.cfc @@ -91,6 +91,7 @@ component aliases="start" { * @AJPPort AJP Port number * @javaVersion Any endpoint ID, such as "java:openjdk11" from the Java endpoint * @javaVersion.optionsUDF javaVersionComplete + * @comamnd Experimental feature to return raw command for starting server. Pass true/false **/ function run( String name, @@ -132,7 +133,8 @@ component aliases="start" { String javaHomeDirectory, Boolean AJPEnable, Numeric AJPPort, - String javaVersion + String javaVersion, + Boolean command ){ // This is a common mis spelling From e4274a99aef0435a85ea7994c24825bd352445b1 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 13 Jan 2020 20:29:14 -0600 Subject: [PATCH 059/102] Update readme.md --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 43e21947f..850a95599 100644 --- a/readme.md +++ b/readme.md @@ -19,9 +19,9 @@ CommandBox ColdFusion (CFML) CLI, Package Manager, REPL and much more! Official stable releases can be found at the [CommandBox Official Page](http://www.ortussolutions.com/products/commandbox#download) -**Integration Releases** +**Snapshot Releases** -Download from the [Ortus Integration Repository](http://integration.stg.ortussolutions.com/artifacts/ortussolutions/commandbox/). +Download from the [Ortus Download Site](https://downloads.ortussolutions.com/#/ortussolutions/commandbox/). *Just please note that this contains latest bleeding edge releases that might not be stable.* From 8233ca130ea075eb194f97a46dc6f13da112764e Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 14 Jan 2020 16:55:56 -0600 Subject: [PATCH 060/102] COMMANDBOX-1081 --- src/cfml/system/BaseCommand.cfc | 2 +- src/cfml/system/modules_app/system-commands/commands/quit.cfc | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cfml/system/BaseCommand.cfc b/src/cfml/system/BaseCommand.cfc index 8195a520a..7c2f3ee2a 100644 --- a/src/cfml/system/BaseCommand.cfc +++ b/src/cfml/system/BaseCommand.cfc @@ -61,7 +61,7 @@ component accessors="true" singleton { return variables.exitCode; } - function setExitCode( exitCode ) { + function setExitCode( required numeric exitCode ) { variables.exitCode = arguments.exitCode; } diff --git a/src/cfml/system/modules_app/system-commands/commands/quit.cfc b/src/cfml/system/modules_app/system-commands/commands/quit.cfc index 0319a0d13..a6a7436b0 100644 --- a/src/cfml/system/modules_app/system-commands/commands/quit.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/quit.cfc @@ -23,6 +23,9 @@ component aliases="exit,q,e" { * @exitCode The exitCode for the box process to return */ function run( exitCode=0 ) { + if( !isNumeric( exitCode ) ) { + error( 'Exit code [#exitCode#] is invalid. Please supply a numeric input' ); + } setExitCode( exitCode ); shell.exit(); } From 913846a1cd4e0cd8c49767bf88d0a63c071ac040 Mon Sep 17 00:00:00 2001 From: "Matthew J. Clemente" Date: Tue, 14 Jan 2020 17:58:54 -0500 Subject: [PATCH 061/102] Use relative directory when scaffolding model with resource command (#213) --- .../commands/coldbox/create/resource.cfc | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/resource.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/resource.cfc index ff37d60d2..7f2a9a786 100644 --- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/resource.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/resource.cfc @@ -115,9 +115,10 @@ component { } else { arguments.handlersDirectory = resolvePath( arguments.handlersDirectory ); arguments.viewsDirectory = resolvePath( arguments.viewsDirectory ); + var relativeModelsDirectory = arguments.modelsDirectory; arguments.modelsDirectory = resolvePath( arguments.modelsDirectory ); } - + /********************** GENERATE HANDLER ************************/ print.greenBoldLine( "Generating #arguments.resource# resources..." ); @@ -200,10 +201,10 @@ component { // Generate model command( "coldbox create model" ) .params( - name = ucFirst( arguments.singularName ), - description = "I model a #arguments.singularName#", - properties = arguments.properties, - directory = arguments.modelsDirectory, + name = ucFirst( arguments.singularName ), + description = "I model a #arguments.singularName#", + properties = arguments.properties, + directory = relativeModelsDirectory, testsDirectory = arguments.specsDirectory & '/unit' ) .run(); @@ -216,7 +217,7 @@ component { persistence = "singleton", description = "I manage #arguments.singularName#", methods = "save,delete,list,get", - directory = arguments.modelsDirectory, + directory = relativeModelsDirectory, testsDirectory = arguments.specsDirectory & '/unit' ) .run(); From 382a0a3f26fefd86cbe7733f7fbcbda33b41f742 Mon Sep 17 00:00:00 2001 From: John Berquist Date: Thu, 16 Jan 2020 11:29:03 -0800 Subject: [PATCH 062/102] Improve command arg escaping (#212) * Improve arg escaping This approach improves compatibility for cmd, powershell, and bash. * Alternate approach to command string escaping This approach would require the user to specify which shell to target: ``` server start command=bash ``` --- src/cfml/system/services/ServerService.cfc | 65 +++++++++++++++++++++- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index abb9010d5..b1da078d6 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -1265,10 +1265,10 @@ component accessors="true" singleton { var cleanedArgs = cr & ' ' & trim( reReplaceNoCase( args.toList( ' ' ), ' (-|"-)', cr & ' \1', 'all' ) ); job.addLog("Server start command: #cleanedargs#"); } - - if( serverProps.command ?: false ) { + + if( serverProps.keyExists( 'command' ) ) { job.complete( serverInfo.debug ); - return args.map( ( arg ) => '"#arg#"' ).toList( ' ' ); + return args.map( escapeCommandArgs( serverProps.command ) ).toList( ' ' ); } processBuilder.init( args ); @@ -2145,4 +2145,63 @@ component accessors="true" singleton { return props; } + + function escapeCommandArgs( required string targetShell ) { + // regex to split a string into quoted and unquoted segments + var segmentRegex = function( escapeChar ) { + if( !isNull( arguments.escapeChar ) ) { + // (?:[^"]|#escapeChar#.)+ + // match anything that is not a quote or is an escape followed by anything + // or + // "(?:[^"]|#escapeChar#.)*" + // match an opening quote, followed by matching anything that is not + // a quote or is an escape followed by anything, followed by a closing quote + return '(?:[^"]|#escapeChar#.)+|"(?:[^"]|#escapeChar#.)*"'; + } + // if no escape char, then just match unquoted and quoted sections + return '[^"]+|"[^"]*"'; + }; + + var shellEscapes = { + bash: function( arg, idx ) { + return toString( arg ).reMatch( segmentRegex( '\\' ) ).map( ( segment ) => { + if( !segment.startswith( '"' ) ) { + segment = segment.replace( ' ', '\ ', 'all' ); + } + return segment; + } ).toList( '' ); + }, + cmd: function( arg, idx ) { + return toString( arg ).reMatch( segmentRegex() ).map( ( segment ) => { + if( !segment.startswith( '"' ) and segment.find( ' ' ) ) { + segment = '"#segment#"'; + } + if( segment.endswith( '\"' ) ) { + // cmd will pass this literally, so we need to escape it for the underlying Java process + segment = segment.left( -2 ) & '\\"'; + } + return segment; + } ).toList( '' ); + }, + pwsh: function( arg, idx ) { + return toString( arg ).reMatch( segmentRegex( '`' ) ).map( ( segment ) => { + if( !segment.startswith( '"' ) ) { + segment = segment.replace( ' ', '` ', 'all' ); + } + // PowerShell needs the `-` in single `-` args escaped when they contain periods + // or it will split the argument at the period + if( segment.reFind( '^-(?!-)' ) && segment.find( '.' ) ) { + segment = '`' & segment; + } + return segment; + } ).toList( '' ); + } + }; + + if( !shellEscapes.keyExists( targetShell ) ) { + throw( message="Invalid target shell specified. [#targetShell#].", type="commandException"); + } + + return shellEscapes[ targetShell ]; + } } From c36be051fb779ad992bd24e30feb5fd5557fd039 Mon Sep 17 00:00:00 2001 From: John Berquist Date: Fri, 17 Jan 2020 16:43:16 -0800 Subject: [PATCH 063/102] Add server command args interceptor (#214) * Add interception point to ServerService.cfc Remove script file logic from ServerService.cfc as that will be handled by an interceptor * Add an interceptor for server start that generates a shell script --- .../server-commands/ModuleConfig.cfc | 3 + .../interceptors/serverCommandLine.cfc | 166 ++++++++++++++++++ .../system/services/InterceptorService.cfc | 2 +- src/cfml/system/services/ServerService.cfc | 86 +++------ 4 files changed, 192 insertions(+), 65 deletions(-) create mode 100644 src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc diff --git a/src/cfml/system/modules_app/server-commands/ModuleConfig.cfc b/src/cfml/system/modules_app/server-commands/ModuleConfig.cfc index 1531d200d..8450033b4 100644 --- a/src/cfml/system/modules_app/server-commands/ModuleConfig.cfc +++ b/src/cfml/system/modules_app/server-commands/ModuleConfig.cfc @@ -7,5 +7,8 @@ */ component { function configure() { + interceptors = [ + { class="#moduleMapping#.interceptors.serverCommandLine" } + ]; } } diff --git a/src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc b/src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc new file mode 100644 index 000000000..caf3bc5c4 --- /dev/null +++ b/src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc @@ -0,0 +1,166 @@ +/** + ********************************************************************************* + * Copyright Since 2014 CommandBox by Ortus Solutions, Corp + * www.coldbox.org | www.ortussolutions.com + ******************************************************************************** + * + * I am an interceptor that listens for the server start command line arguments + * and generates a shell script from them if `scriptFile` is specified. + * + */ +component { + + property name="job" inject="interactiveJob"; + property name="fileSystemUtil" inject="fileSystem"; + property name="CR" inject="CR@constants"; + property name="systemSettings" inject="SystemSettings"; + property name="print" inject="PrintBuffer"; + + variables.shellScripts = { + bash: { ext: 'sh', startlines: [ '##!/bin/bash', '' ], endlines: [] }, + cmd : { ext: 'bat', startlines: [ '@echo off', 'setlocal' ], endlines: [ 'endlocal' ] }, + pwsh: { ext: 'ps1', startlines: [], endlines: [] } + }; + + function onServerCommandLine( struct interceptData ) { + if( interceptData.serverProps.keyExists( 'scriptFile' ) ) { + if( !shellScripts.keyExists( interceptData.serverProps.scriptFile ) ) { + job.addErrorLog( + 'Invalid target shell specified [#interceptData.serverProps.scriptFile#] for command line shell script. Unable to generate script.' + ); + } else { + generateStartScript( + interceptData.commandLineArguments, + interceptData.serverProps.scriptFile, + interceptData.serverProps.scriptFilePath ?: '', + interceptData.serverInfo.serverConfigFile + ); + } + } + } + + private function generateStartScript( + required array commandLineArguments, + required string targetShell, + required string scriptFilePath, + required string serverConfigFile + ) { + if( !scriptFilePath.len() ) { + scriptFilePath = getFileFromPath( serverConfigFile ); + if( scriptFilePath.startsWith( 'server' ) ) { + scriptFilePath = scriptFilePath.replace( 'server', 'server-start' ); + } else { + scriptFilePath = 'server-start-' & scriptFilePath; + } + scriptFilePath = scriptFilePath.left( -4 ) & shellScripts[ targetShell ].ext; + scriptFilePath = getDirectoryFromPath( serverConfigFile ) & scriptFilePath; + } + + scriptFilePath = fileSystemUtil.resolvePath( scriptFilePath ); + + var cmdLines = []; + cmdLines.append( shellScripts[ targetShell ].startlines, true ); + cmdLines.append( encodeShellEnv( targetShell ), true ); + cmdLines.append( encodeShellCmd( commandLineArguments, targetShell ) ); + cmdLines.append( shellScripts[ targetShell ].endlines, true ); + fileWrite( scriptFilePath, cmdLines.toList( cr ) & cr ); + + job.addLog( 'Start script for shell [#targetShell#] generated at: #scriptFilePath#' ); + } + + private function encodeShellEnv( required string targetShell ) { + var cmdEnv = systemSettings.getAllEnvironmentsFlattened(); + + var shellEncoders = { + bash:function( result, key, value ) { + result.append( 'export #key#="#value.replace( '"', '\"', 'all' )#"' ); + return result; + }, + cmd:function( result, key, value ) { + result.append( 'set #key#=#value#' ); + return result; + }, + pwsh:function( result, key, value ) { + result.append( '$env:#key#="#value.replace( '"', '`"', 'all' )#"' ); + return result; + } + }; + + return cmdEnv.reduce( shellEncoders[ targetShell ], [] ); + } + + private function encodeShellCmd( required array args, required string targetShell ) { + var shellNewlineEscape = { bash: '\', cmd: '^', pwsh: '`' }; + var newLineSep = ' ' & shellNewlineEscape[ targetShell ] & cr & chr( 9 ); + var reducer = ( r, a ) => r & ( a.startswith( '-' ) ? newLineSep : ' ' ) & a; + return args + .map( escapeCommandArgs( targetShell ) ) + .reduce( reducer, '' ) + .ltrim(); + } + + private function escapeCommandArgs( required string targetShell ) { + // regex to split a string into quoted and unquoted segments + var segmentRegex = function( escapeChar ) { + if( !isNull( arguments.escapeChar ) ) { + // (?:[^"]|#escapeChar#.)+ + // match anything that is not a quote or is an escape followed by anything + // or + // "(?:[^"]|#escapeChar#.)*" + // match an opening quote, followed by matching anything that is not + // a quote or is an escape followed by anything, followed by a closing quote + return '(?:[^"]|#escapeChar#.)+|"(?:[^"]|#escapeChar#.)*"'; + } + // if no escape char, then just match unquoted and quoted sections + return '[^"]+|"[^"]*"'; + }; + + var shellEscapes = { + bash:function( arg, idx ) { + return toString( arg ) + .reMatch( segmentRegex( '\\' ) ) + .map( ( segment ) => { + if( !segment.startswith( '"' ) ) { + segment = segment.replace( ' ', '\ ', 'all' ); + } + return segment; + } ) + .toList( '' ); + }, + cmd:function( arg, idx ) { + return toString( arg ) + .reMatch( segmentRegex() ) + .map( ( segment ) => { + if( !segment.startswith( '"' ) and segment.find( ' ' ) ) { + segment = '"#segment#"'; + } + if( segment.endswith( '\"' ) ) { + // cmd will pass this literally, so we need to escape it for the underlying Java process + segment = segment.left( -2 ) & '\\"'; + } + return segment; + } ) + .toList( '' ); + }, + pwsh:function( arg, idx ) { + return toString( arg ) + .reMatch( segmentRegex( '`' ) ) + .map( ( segment ) => { + if( !segment.startswith( '"' ) ) { + segment = segment.replace( ' ', '` ', 'all' ); + } + // PowerShell needs the `-` in single `-` args escaped when they contain periods + // or it will split the argument at the period + if( segment.reFind( '^-(?!-)' ) && segment.find( '.' ) ) { + segment = '`' & segment; + } + return segment; + } ) + .toList( '' ); + } + }; + + return shellEscapes[ targetShell ]; + } + +} diff --git a/src/cfml/system/services/InterceptorService.cfc b/src/cfml/system/services/InterceptorService.cfc index 3bfa73aa8..94cb41841 100644 --- a/src/cfml/system/services/InterceptorService.cfc +++ b/src/cfml/system/services/InterceptorService.cfc @@ -33,7 +33,7 @@ component accessors=true singleton { // Module lifecycle 'preModuleLoad','postModuleLoad','preModuleUnLoad','postModuleUnload', // Server lifecycle - 'preServerStart','onServerStart','onServerInstall','onServerStop','preServerForget','postServerForget', + 'preServerStart','onServerStart','onServerInstall','onServerCommandLine','onServerStop','preServerForget','postServerForget', // Error handling 'onException', // Package lifecycle diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index b1da078d6..9904120f8 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -1261,31 +1261,47 @@ component accessors="true" singleton { args.prepend( expandPath( '/bin/bash' ) ); } + // At this point all command line arguments are in place, announce this + var interceptData = { + commandLineArguments=args, + serverInfo=serverInfo, + serverJSON=serverJSON, + defaults=defaults, + serverProps=serverProps, + serverDetails=serverDetails, + installDetails=installDetails ?: {} + }; + interceptorService.announceInterception( 'onServerCommandLine', interceptData ); + // ensure we get the updated args if they were replaced wholesale by interceptor + args = interceptData.commandLineArguments; + + // now we can log the *final* command line string that will be used to start the server if( serverInfo.debug ) { var cleanedArgs = cr & ' ' & trim( reReplaceNoCase( args.toList( ' ' ), ' (-|"-)', cr & ' \1', 'all' ) ); job.addLog("Server start command: #cleanedargs#"); } - if( serverProps.keyExists( 'command' ) ) { + if( serverProps.dryRun ?: false ) { + job.addLog( 'Dry run specified, exiting without starting server.' ); job.complete( serverInfo.debug ); - return args.map( escapeCommandArgs( serverProps.command ) ).toList( ' ' ); + return; } processBuilder.init( args ); - + // incorporate CommandBox environment variables into the process's env var currentEnv = processBuilder.environment(); currentEnv.putAll( systemSettings.getAllEnvironmentsFlattened() ); - + // Special check to remove ConEMU vars which can screw up the sub process if it happens to run cmd, such as opening VSCode. if( fileSystemUtil.isWindows() && currentEnv.containsKey( 'ConEmuPID' ) ) { for( var key in currentEnv ) { if( key.startsWith( 'ConEmu' ) || key == 'PROMPT' ) { currentEnv.remove( key ); - } + } } } - + // Conjoin standard error and output for convenience. processBuilder.redirectErrorStream( true ); // Kick off actual process @@ -2146,62 +2162,4 @@ component accessors="true" singleton { return props; } - function escapeCommandArgs( required string targetShell ) { - // regex to split a string into quoted and unquoted segments - var segmentRegex = function( escapeChar ) { - if( !isNull( arguments.escapeChar ) ) { - // (?:[^"]|#escapeChar#.)+ - // match anything that is not a quote or is an escape followed by anything - // or - // "(?:[^"]|#escapeChar#.)*" - // match an opening quote, followed by matching anything that is not - // a quote or is an escape followed by anything, followed by a closing quote - return '(?:[^"]|#escapeChar#.)+|"(?:[^"]|#escapeChar#.)*"'; - } - // if no escape char, then just match unquoted and quoted sections - return '[^"]+|"[^"]*"'; - }; - - var shellEscapes = { - bash: function( arg, idx ) { - return toString( arg ).reMatch( segmentRegex( '\\' ) ).map( ( segment ) => { - if( !segment.startswith( '"' ) ) { - segment = segment.replace( ' ', '\ ', 'all' ); - } - return segment; - } ).toList( '' ); - }, - cmd: function( arg, idx ) { - return toString( arg ).reMatch( segmentRegex() ).map( ( segment ) => { - if( !segment.startswith( '"' ) and segment.find( ' ' ) ) { - segment = '"#segment#"'; - } - if( segment.endswith( '\"' ) ) { - // cmd will pass this literally, so we need to escape it for the underlying Java process - segment = segment.left( -2 ) & '\\"'; - } - return segment; - } ).toList( '' ); - }, - pwsh: function( arg, idx ) { - return toString( arg ).reMatch( segmentRegex( '`' ) ).map( ( segment ) => { - if( !segment.startswith( '"' ) ) { - segment = segment.replace( ' ', '` ', 'all' ); - } - // PowerShell needs the `-` in single `-` args escaped when they contain periods - // or it will split the argument at the period - if( segment.reFind( '^-(?!-)' ) && segment.find( '.' ) ) { - segment = '`' & segment; - } - return segment; - } ).toList( '' ); - } - }; - - if( !shellEscapes.keyExists( targetShell ) ) { - throw( message="Invalid target shell specified. [#targetShell#].", type="commandException"); - } - - return shellEscapes[ targetShell ]; - } } From d70bfd5b82e335ea1f88622e4629f4e267c20acc Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 17 Jan 2020 18:47:24 -0600 Subject: [PATCH 064/102] Change interception name --- .../server-commands/interceptors/serverCommandLine.cfc | 2 +- src/cfml/system/services/InterceptorService.cfc | 2 +- src/cfml/system/services/ServerService.cfc | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc b/src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc index caf3bc5c4..c55faa9bd 100644 --- a/src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc +++ b/src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc @@ -22,7 +22,7 @@ component { pwsh: { ext: 'ps1', startlines: [], endlines: [] } }; - function onServerCommandLine( struct interceptData ) { + function onServerProcessLaunch( struct interceptData ) { if( interceptData.serverProps.keyExists( 'scriptFile' ) ) { if( !shellScripts.keyExists( interceptData.serverProps.scriptFile ) ) { job.addErrorLog( diff --git a/src/cfml/system/services/InterceptorService.cfc b/src/cfml/system/services/InterceptorService.cfc index 94cb41841..11b04f01b 100644 --- a/src/cfml/system/services/InterceptorService.cfc +++ b/src/cfml/system/services/InterceptorService.cfc @@ -33,7 +33,7 @@ component accessors=true singleton { // Module lifecycle 'preModuleLoad','postModuleLoad','preModuleUnLoad','postModuleUnload', // Server lifecycle - 'preServerStart','onServerStart','onServerInstall','onServerCommandLine','onServerStop','preServerForget','postServerForget', + 'preServerStart','onServerStart','onServerInstall','onServerProcessLaunch','onServerStop','preServerForget','postServerForget', // Error handling 'onException', // Package lifecycle diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 9904120f8..537aa525d 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -1271,7 +1271,7 @@ component accessors="true" singleton { serverDetails=serverDetails, installDetails=installDetails ?: {} }; - interceptorService.announceInterception( 'onServerCommandLine', interceptData ); + interceptorService.announceInterception( 'onServerProcessLaunch', interceptData ); // ensure we get the updated args if they were replaced wholesale by interceptor args = interceptData.commandLineArguments; From 8a3f5dcdc1e24ac2e28952b44e76f4dce39912ea Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 17 Jan 2020 19:11:08 -0600 Subject: [PATCH 065/102] COMMANDBOX-1083 --- .../server-commands/commands/server/start.cfc | 9 +++-- .../interceptors/serverCommandLine.cfc | 34 +++++++++---------- src/cfml/system/services/ServerService.cfc | 2 +- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/cfml/system/modules_app/server-commands/commands/server/start.cfc b/src/cfml/system/modules_app/server-commands/commands/server/start.cfc index 29c1b8abb..48dcb6a70 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/start.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/start.cfc @@ -91,7 +91,10 @@ component aliases="start" { * @AJPPort AJP Port number * @javaVersion Any endpoint ID, such as "java:openjdk11" from the Java endpoint * @javaVersion.optionsUDF javaVersionComplete - * @comamnd Experimental feature to return raw command for starting server. Pass true/false + * @startScript If you want to generate a native script to directly start the server process pass bash, cmd, or pwsh + * @startScript.options bash,cmd,pwsh + * @startScriptFile Optional override for the name and location of the start script. This is ignored if no startScript param is specified + * @dryRun Pass true to abort actually starting the server process, but all instalation and downloading will still be performed to "warm up" the engine installation. **/ function run( String name, @@ -134,7 +137,9 @@ component aliases="start" { Boolean AJPEnable, Numeric AJPPort, String javaVersion, - Boolean command + String scriptFile, + String scriptFilePath, + Boolean dryRun ){ // This is a common mis spelling diff --git a/src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc b/src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc index c55faa9bd..a06cbba66 100644 --- a/src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc +++ b/src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc @@ -5,7 +5,7 @@ ******************************************************************************** * * I am an interceptor that listens for the server start command line arguments - * and generates a shell script from them if `scriptFile` is specified. + * and generates a shell script from them if `startScript` is specified. * */ component { @@ -23,16 +23,16 @@ component { }; function onServerProcessLaunch( struct interceptData ) { - if( interceptData.serverProps.keyExists( 'scriptFile' ) ) { - if( !shellScripts.keyExists( interceptData.serverProps.scriptFile ) ) { + if( interceptData.serverProps.keyExists( 'startScript' ) ) { + if( !shellScripts.keyExists( interceptData.serverProps.startScript ) ) { job.addErrorLog( - 'Invalid target shell specified [#interceptData.serverProps.scriptFile#] for command line shell script. Unable to generate script.' + 'Invalid target shell specified [#interceptData.serverProps.startScript#] for command line shell script. Unable to generate script.' ); } else { generateStartScript( interceptData.commandLineArguments, - interceptData.serverProps.scriptFile, - interceptData.serverProps.scriptFilePath ?: '', + interceptData.serverProps.startScript, + interceptData.serverProps.startScriptFile ?: '', interceptData.serverInfo.serverConfigFile ); } @@ -42,30 +42,30 @@ component { private function generateStartScript( required array commandLineArguments, required string targetShell, - required string scriptFilePath, + required string startScriptFile, required string serverConfigFile ) { - if( !scriptFilePath.len() ) { - scriptFilePath = getFileFromPath( serverConfigFile ); - if( scriptFilePath.startsWith( 'server' ) ) { - scriptFilePath = scriptFilePath.replace( 'server', 'server-start' ); + if( !startScriptFile.len() ) { + startScriptFile = getFileFromPath( serverConfigFile ); + if( startScriptFile.startsWith( 'server' ) ) { + startScriptFile = startScriptFile.replace( 'server', 'server-start' ); } else { - scriptFilePath = 'server-start-' & scriptFilePath; + startScriptFile = 'server-start-' & startScriptFile; } - scriptFilePath = scriptFilePath.left( -4 ) & shellScripts[ targetShell ].ext; - scriptFilePath = getDirectoryFromPath( serverConfigFile ) & scriptFilePath; + startScriptFile = startScriptFile.left( -4 ) & shellScripts[ targetShell ].ext; + startScriptFile = getDirectoryFromPath( serverConfigFile ) & startScriptFile; } - scriptFilePath = fileSystemUtil.resolvePath( scriptFilePath ); + startScriptFile = fileSystemUtil.resolvePath( startScriptFile ); var cmdLines = []; cmdLines.append( shellScripts[ targetShell ].startlines, true ); cmdLines.append( encodeShellEnv( targetShell ), true ); cmdLines.append( encodeShellCmd( commandLineArguments, targetShell ) ); cmdLines.append( shellScripts[ targetShell ].endlines, true ); - fileWrite( scriptFilePath, cmdLines.toList( cr ) & cr ); + fileWrite( startScriptFile, cmdLines.toList( cr ) & cr ); - job.addLog( 'Start script for shell [#targetShell#] generated at: #scriptFilePath#' ); + job.addLog( 'Start script for shell [#targetShell#] generated at: #startScriptFile#' ); } private function encodeShellEnv( required string targetShell ) { diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 537aa525d..b67819c6b 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -337,7 +337,7 @@ component accessors="true" singleton { // Save hand-entered properties in our server.json for next time for( var prop in serverProps ) { // Ignore null props or ones that shouldn't be saved - if( isNull( serverProps[ prop ] ) || listFindNoCase( 'saveSettings,serverConfigFile,debug,force,console,trace,command', prop ) ) { + if( isNull( serverProps[ prop ] ) || listFindNoCase( 'saveSettings,serverConfigFile,debug,force,console,trace,startScript,startScriptFile,dryRun', prop ) ) { continue; } var configPath = replace( fileSystemUtil.resolvePath( defaultServerConfigFileDirectory ), '\', '/', 'all' ); From 341f490fab960eb6179dfb903a246da8c79beb21 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 17 Jan 2020 19:12:36 -0600 Subject: [PATCH 066/102] COMMANDBOX-1083 --- .../modules_app/server-commands/commands/server/start.cfc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cfml/system/modules_app/server-commands/commands/server/start.cfc b/src/cfml/system/modules_app/server-commands/commands/server/start.cfc index 48dcb6a70..8eee65e32 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/start.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/start.cfc @@ -137,8 +137,8 @@ component aliases="start" { Boolean AJPEnable, Numeric AJPPort, String javaVersion, - String scriptFile, - String scriptFilePath, + String startScript, + String startScriptFile, Boolean dryRun ){ From 73452de2e72dfc72429464537eff191d6e01ab1d Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 17 Jan 2020 20:02:57 -0600 Subject: [PATCH 067/102] COMMANDBOX-1083 --- .../server-commands/interceptors/serverCommandLine.cfc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc b/src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc index a06cbba66..7cc11bda9 100644 --- a/src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc +++ b/src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc @@ -77,7 +77,7 @@ component { return result; }, cmd:function( result, key, value ) { - result.append( 'set #key#=#value#' ); + result.append( 'set #key#=#value.reReplace( '([<>\|\&\^])', '^\1', 'all' )#' ); return result; }, pwsh:function( result, key, value ) { @@ -133,6 +133,8 @@ component { .map( ( segment ) => { if( !segment.startswith( '"' ) and segment.find( ' ' ) ) { segment = '"#segment#"'; + } else { + segment = segment.reReplace( '([<>\|\&\^])', '^\1', 'all' ); } if( segment.endswith( '\"' ) ) { // cmd will pass this literally, so we need to escape it for the underlying Java process From ac7f47030d1f4d2dfd49d1e9ddab7ee0a604a277 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Sat, 18 Jan 2020 12:12:48 -0600 Subject: [PATCH 068/102] Experiementing with cmd escapes --- .../interceptors/serverCommandLine.cfc | 38 ++++++++++--------- src/cfml/system/services/ServerService.cfc | 7 ++-- src/cfml/system/util/Parser.cfc | 4 +- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc b/src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc index 7cc11bda9..33a8c6ffe 100644 --- a/src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc +++ b/src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc @@ -77,7 +77,20 @@ component { return result; }, cmd:function( result, key, value ) { - result.append( 'set #key#=#value.reReplace( '([<>\|\&\^])', '^\1', 'all' )#' ); + var escapedvalue = ''; + var inQuotes = false; + for( var char in toString( value ).listToArray( '' ) ) { + if( char == '"' ) { + inQuotes = !inQuotes; + } + // Escape special chars <>|& but only if not currently inside quotes + if( inQuotes ) { + escapedvalue &= char; + } else { + escapedvalue &= char.reReplace( '([<>\|\&\^])', '^\1', 'all' ); + } + } + result.append( 'set #key#=#escapedvalue#' ); return result; }, pwsh:function( result, key, value ) { @@ -92,7 +105,7 @@ component { private function encodeShellCmd( required array args, required string targetShell ) { var shellNewlineEscape = { bash: '\', cmd: '^', pwsh: '`' }; var newLineSep = ' ' & shellNewlineEscape[ targetShell ] & cr & chr( 9 ); - var reducer = ( r, a ) => r & ( a.startswith( '-' ) ? newLineSep : ' ' ) & a; + var reducer = ( r, a ) => r & ( ( a.startswith( '-' ) || a.startswith( '"-' ) ) ? newLineSep : ' ' ) & a; return args .map( escapeCommandArgs( targetShell ) ) .reduce( reducer, '' ) @@ -128,21 +141,12 @@ component { .toList( '' ); }, cmd:function( arg, idx ) { - return toString( arg ) - .reMatch( segmentRegex() ) - .map( ( segment ) => { - if( !segment.startswith( '"' ) and segment.find( ' ' ) ) { - segment = '"#segment#"'; - } else { - segment = segment.reReplace( '([<>\|\&\^])', '^\1', 'all' ); - } - if( segment.endswith( '\"' ) ) { - // cmd will pass this literally, so we need to escape it for the underlying Java process - segment = segment.left( -2 ) & '\\"'; - } - return segment; - } ) - .toList( '' ); + var segment = toString( arg ); + // Wrap this arg up in quotes and double up any quotes already inside of it + segment = '"#segment.replace( '"', '""', 'all' )#"'; + // Also any literal values of \" need to be turned into \\" + segment = segment.replace( '\"', '\\"', 'all' ); + return segment; }, pwsh:function( arg, idx ) { return toString( arg ) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index b67819c6b..7c35a2a3a 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -268,7 +268,7 @@ component accessors="true" singleton { var serverInfo = serverDetails.serverinfo; // If the server is already running, make sure the user really wants to do this. - if( isServerRunning( serverInfo ) && !(serverProps.force ?: false ) ) { + if( isServerRunning( serverInfo ) && !(serverProps.force ?: false ) && !(serverProps.dryRun ?: false ) ) { job.addErrorLog( 'Server "#serverInfo.name#" (#serverInfo.webroot#) is already running!' ); job.addErrorLog( 'Overwriting a running server means you won''t be able to use the "stop" command to stop the original one.' ); job.addWarnLog( 'Use the --force parameter to skip this check.' ); @@ -1032,12 +1032,11 @@ component accessors="true" singleton { // This is an array of tokens to send to the process builder var args = []; - // "borrow" the CommandBox commandline parser to tokenize the JVM args. Not perfect, but close. Handles quoted values with spaces. var argTokens = parser.tokenizeInput( serverInfo.JVMargs ) .map( function( i ){ - // Clean up a couple escapes the parser does that we don't need - return parser.unwrapQuotes( i.replace( '\=', '=', 'all' ).replace( '\\', '\', 'all' ).replace( ';', '\;', 'all' ) ); + // unwrap quotes, and unescape any special chars like \" inside the string + return parser.replaceEscapedChars( parser.removeEscapedChars( parser.unwrapQuotes( i ) ) ); }); diff --git a/src/cfml/system/util/Parser.cfc b/src/cfml/system/util/Parser.cfc index 88e92e101..e294fd06a 100644 --- a/src/cfml/system/util/Parser.cfc +++ b/src/cfml/system/util/Parser.cfc @@ -294,7 +294,7 @@ component { // ----------------------------- Private --------------------------------------------- - private function removeEscapedChars( theString ) { + function removeEscapedChars( theString ) { theString = replaceNoCase( theString, "\\", '__backSlash__', "all" ); theString = replaceNoCase( theString, "\'", '__singleQuote__', "all" ); theString = replaceNoCase( theString, '\"', '__doubleQuote__', "all" ); @@ -306,7 +306,7 @@ component { return replaceNoCase( theString, '\|', '__pipe__', "all" ); } - private function replaceEscapedChars( theString ) { + function replaceEscapedChars( theString ) { theString = replaceNoCase( theString, '__backSlash__', "\", "all" ); theString = replaceNoCase( theString, '__singleQuote__', "'", "all" ); theString = replaceNoCase( theString, '__doubleQuote__', '"', "all" ); From d56bb22de74604e0796e7d5c989fbc2bbcee7bbb Mon Sep 17 00:00:00 2001 From: John Berquist Date: Sat, 18 Jan 2020 16:52:39 -0800 Subject: [PATCH 069/102] More shell escape improvements (#215) This update should hopefully help both PowerShell and Bash scripts work properly. Both shells can use single quotes to fully escape all characters inside (except the ' char itself, which is handled differently by each shell). PowerShell also needs extra escaping of double quotes and double quotes preceded by backslashes to allow the underlying Java process to correctly process the arguments. Also added a % to %% escape for CMD since it always treats them as special characters in batch scripts - even though it doesn't on the command line itself. Finally added a check for `'-` (in addition to the checks for `-` and `"-`) in the logic that splits the command string onto multiple lines. I believe I have both PowerShell and Bash working with the example `.env` file from slack. --- .../interceptors/serverCommandLine.cfc | 85 ++++++++----------- 1 file changed, 36 insertions(+), 49 deletions(-) diff --git a/src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc b/src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc index 33a8c6ffe..7fbd3fea6 100644 --- a/src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc +++ b/src/cfml/system/modules_app/server-commands/interceptors/serverCommandLine.cfc @@ -72,11 +72,15 @@ component { var cmdEnv = systemSettings.getAllEnvironmentsFlattened(); var shellEncoders = { - bash:function( result, key, value ) { - result.append( 'export #key#="#value.replace( '"', '\"', 'all' )#"' ); + bash: function( result, key, value ) { + // in bash, only a-z, A-Z, _ and 0-9 are allowed for env variables + // so java props with `.` in the key are out + if( reFind( '^[a-zA-Z_]+[a-zA-Z0-9_]*$', key ) ) { + result.append( "export #key#='#value.replace( "'", "'\''", "all" )#'" ); + } return result; }, - cmd:function( result, key, value ) { + cmd: function( result, key, value ) { var escapedvalue = ''; var inQuotes = false; for( var char in toString( value ).listToArray( '' ) ) { @@ -87,14 +91,16 @@ component { if( inQuotes ) { escapedvalue &= char; } else { - escapedvalue &= char.reReplace( '([<>\|\&\^])', '^\1', 'all' ); + escapedvalue &= char.reReplace( '([<>\|\&\^])', '^\1', 'all' ); } } - result.append( 'set #key#=#escapedvalue#' ); + result.append( 'set #key#=#escapedvalue.replace( "%", "%%", "all" )#' ); return result; }, - pwsh:function( result, key, value ) { - result.append( '$env:#key#="#value.replace( '"', '`"', 'all' )#"' ); + pwsh: function( result, key, value ) { + key = "${env:#key.reReplace( '([{}])', '`\1', 'all' )#}"; + value = "'" & value.replace( "'", "''", "all" ) & "'"; + result.append( '#key#=#value#' ); return result; } }; @@ -105,7 +111,7 @@ component { private function encodeShellCmd( required array args, required string targetShell ) { var shellNewlineEscape = { bash: '\', cmd: '^', pwsh: '`' }; var newLineSep = ' ' & shellNewlineEscape[ targetShell ] & cr & chr( 9 ); - var reducer = ( r, a ) => r & ( ( a.startswith( '-' ) || a.startswith( '"-' ) ) ? newLineSep : ' ' ) & a; + var reducer = ( r, a ) => r & ( a.reFind( '^[''"]?-' ) ? newLineSep : ' ' ) & a; return args .map( escapeCommandArgs( targetShell ) ) .reduce( reducer, '' ) @@ -113,56 +119,37 @@ component { } private function escapeCommandArgs( required string targetShell ) { - // regex to split a string into quoted and unquoted segments - var segmentRegex = function( escapeChar ) { - if( !isNull( arguments.escapeChar ) ) { - // (?:[^"]|#escapeChar#.)+ - // match anything that is not a quote or is an escape followed by anything - // or - // "(?:[^"]|#escapeChar#.)*" - // match an opening quote, followed by matching anything that is not - // a quote or is an escape followed by anything, followed by a closing quote - return '(?:[^"]|#escapeChar#.)+|"(?:[^"]|#escapeChar#.)*"'; - } - // if no escape char, then just match unquoted and quoted sections - return '[^"]+|"[^"]*"'; - }; - var shellEscapes = { - bash:function( arg, idx ) { - return toString( arg ) - .reMatch( segmentRegex( '\\' ) ) - .map( ( segment ) => { - if( !segment.startswith( '"' ) ) { - segment = segment.replace( ' ', '\ ', 'all' ); - } - return segment; - } ) - .toList( '' ); + bash: function( arg, idx ) { + if( idx == 1 ) { + // actual process to run, don't quote this + return toString( arg ).replace( ' ', '\ ', 'all' ); + } + // otherwise, just fully quote with _single_ quotes + return "'" & toString( arg ).replace( "'", "'\''", "all" ) & "'"; }, - cmd:function( arg, idx ) { + cmd: function( arg, idx ) { var segment = toString( arg ); // Wrap this arg up in quotes and double up any quotes already inside of it segment = '"#segment.replace( '"', '""', 'all' )#"'; // Also any literal values of \" need to be turned into \\" segment = segment.replace( '\"', '\\"', 'all' ); + // Any % chars need to be turned into %% + segment = segment.replace( '%', '%%', 'all' ); return segment; }, - pwsh:function( arg, idx ) { - return toString( arg ) - .reMatch( segmentRegex( '`' ) ) - .map( ( segment ) => { - if( !segment.startswith( '"' ) ) { - segment = segment.replace( ' ', '` ', 'all' ); - } - // PowerShell needs the `-` in single `-` args escaped when they contain periods - // or it will split the argument at the period - if( segment.reFind( '^-(?!-)' ) && segment.find( '.' ) ) { - segment = '`' & segment; - } - return segment; - } ) - .toList( '' ); + pwsh: function( arg, idx ) { + var specialChars = '([{}()@|!''"; &`$##])'; + if( idx == 1 ) { + // actual process to run, don't quote this + return toString( arg ).reReplace(specialChars, '`\1', 'all'); + } + // otherwise, just fully quote with single quotes + arg = "'" & toString( arg ).replace( "'", "''", "all" ) & "'"; + // Also any literal values of " need to be escaped for the underlying Java + // process that is called. If the " was preceded by a \ then that needs + // to be escaped as well + return arg.replace( '"', '\"', 'all' ).replace( '\\"', '\\\"', 'all' ); } }; From 68665eb383f655e1c2575ad54d11abe83329618a Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 22 Jan 2020 23:31:52 -0600 Subject: [PATCH 070/102] COMMANDBOX-1086 --- src/cfml/system/config/box.json.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfml/system/config/box.json.txt b/src/cfml/system/config/box.json.txt index 1c8583f44..20db58cf0 100644 --- a/src/cfml/system/config/box.json.txt +++ b/src/cfml/system/config/box.json.txt @@ -2,7 +2,7 @@ "name":"", "version":"1.0.0", "author":"", - "location":"", + "location":"forgeboxStorage", "directory":"", "createPackageDirectory":true, "packageDirectory":"", From 060108f054d3af9d9fac9e7d5a597ac508b73d6c Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 22 Jan 2020 23:33:32 -0600 Subject: [PATCH 071/102] Typos --- .../system/modules_app/server-commands/commands/server/stop.cfc | 2 +- .../modules_app/testbox-commands/models/TestingService.cfc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cfml/system/modules_app/server-commands/commands/server/stop.cfc b/src/cfml/system/modules_app/server-commands/commands/server/stop.cfc index e7195eade..0d5f266fd 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/stop.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/stop.cfc @@ -40,7 +40,7 @@ component aliases="stop" { arguments.serverConfigFile = resolvePath( arguments.serverConfigFile ); } - // Look up the server that we're starting + // Look up the server that we're stopping var servers = { id: serverService.resolveServerDetails( arguments ).serverinfo }; } // End "all" check diff --git a/src/cfml/system/modules_app/testbox-commands/models/TestingService.cfc b/src/cfml/system/modules_app/testbox-commands/models/TestingService.cfc index 55f51c81d..baebc7fac 100644 --- a/src/cfml/system/modules_app/testbox-commands/models/TestingService.cfc +++ b/src/cfml/system/modules_app/testbox-commands/models/TestingService.cfc @@ -27,7 +27,7 @@ component accessors="true" singleton { public function getTestBoxRunner( required string directory, string slug='' ) { // Get box.json, create empty if it doesn't exist var boxJSON = packageService.readPackageDescriptor( arguments.directory ); - // Get reference to appropriate depenency struct + // Get reference to appropriate dependency struct var runners = boxJSON.testbox.runner ?: ''; var runnerURL = ''; From d81163209c48f41cf93edd87aab45907830e40ea Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 22 Jan 2020 23:35:56 -0600 Subject: [PATCH 072/102] COMMANDBOX-1087 --- src/cfml/system/endpoints/Jar.cfc | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/cfml/system/endpoints/Jar.cfc b/src/cfml/system/endpoints/Jar.cfc index a3f53f85a..4a4c59683 100644 --- a/src/cfml/system/endpoints/Jar.cfc +++ b/src/cfml/system/endpoints/Jar.cfc @@ -77,24 +77,24 @@ component accessors=true implements="IEndpoint" singleton { public function getDefaultName( required string package ) { - var baseURL = arguments.package; + // Strip protocol and host to reveal just path and query string + package = package.reReplaceNoCase( '^([\w:]+)?//.*?/', '' ); - // strip query string, unless it possibly contains .jar like so: + // Check and see if the name of the jar appears somewhere in the URL and use that as the pacakge name // https://search.maven.org/remotecontent?filepath=jline/jline/3.0.0.M1/jline-3.0.0.M1.jar - if( !right( arguments.package, 4 ) == '.jar' ) { - baseURL = listFirst( arguments.package, '?' ); - } - - // Find last segment of URL (may or may not be a file) - var fileName = listLast( baseURL, '/' ); - - // Check for file extension in URL - var fileNameListLen = listLen( fileName, '.' ); - if( fileNameListLen > 1 && listLast( fileName, '.' ) == 'jar' ) { - return listDeleteAt( fileName, fileNameListLen, '.' ); - } + // https://site.com/path/to/package-1.0.0.jar + + // If we see /foo.jar or name=foo.jar or ?foo.jar + if( package.reFindNoCase( '[/\?=](.*\.jar)' ) ) { + // Then strip the name and remove extension + // Note the first .* is greedy so in the case of + // https://site.com/path/to/file.jar?name=custom.jar + // the regex will extract the last match, i.e. "custom" + return package.reReplaceNoCase( '.*[/\?=](.*\.jar).*', '\1' ).left( -4 ); + } + // We give up, so just make the entire URL a slug - return reReplaceNoCase( baseURL, '[^a-zA-Z0-9]', '', 'all' ); + return reReplaceNoCase( package, '[^a-zA-Z0-9]', '', 'all' ); } public function getUpdate( required string package, required string version, boolean verbose=false ) { From 3dd8c8da647a8826333bf36bbda7712062d1bec1 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 22 Jan 2020 23:42:13 -0600 Subject: [PATCH 073/102] COMMANDBOX-1088 --- src/cfml/system/endpoints/ForgeBox.cfc | 40 ++++--- src/cfml/system/endpoints/Lex.cfc | 112 ++++++++++++++++++++ src/cfml/system/services/PackageService.cfc | 47 ++++++-- 3 files changed, 175 insertions(+), 24 deletions(-) create mode 100644 src/cfml/system/endpoints/Lex.cfc diff --git a/src/cfml/system/endpoints/ForgeBox.cfc b/src/cfml/system/endpoints/ForgeBox.cfc index 9b60b4e0b..26fbb3298 100644 --- a/src/cfml/system/endpoints/ForgeBox.cfc +++ b/src/cfml/system/endpoints/ForgeBox.cfc @@ -21,6 +21,7 @@ component accessors="true" implements="IEndpointInteractive" { property name="endpointService" inject="endpointService"; property name="fileSystemUtil" inject="FileSystem"; property name="fileEndpoint" inject="commandbox.system.endpoints.File"; + property name="lexEndpoint" inject="commandbox.system.endpoints.Lex"; property name='pathPatternMatcher' inject='provider:pathPatternMatcher@globber'; property name='wirebox' inject='wirebox'; @@ -354,7 +355,7 @@ component accessors="true" implements="IEndpointInteractive" { * @package The full endpointID like foo@1.0.0 */ public function parseSlug( required string package ) { - var matches = REFindNoCase( "^([a-zA-Z][\w\-\.]*(?:\@(?!stable\b)(?!be\b)[a-zA-Z][\w\-]*)?)(?:\@(.+))?$", package, 1, true ); + var matches = REFindNoCase( "^([\w\-\.]+(?:\@(?!stable\b)(?!be\b)[a-zA-Z][\w\-]*)?)(?:\@(.+))?$", package, 1, true ); if ( arrayLen( matches.len ) < 2 ) { throw( type = "endpointException", @@ -371,7 +372,7 @@ component accessors="true" implements="IEndpointInteractive" { public function parseVersion( required string package ) { var version = 'stable'; // foo@1.0.0 - var matches = REFindNoCase( "^([a-zA-Z][\w\-\.]*(?:\@(?!stable\b)(?!be\b)[a-zA-Z][\w\-]*)?)(?:\@(.+))?$", package, 1, true ); + var matches = REFindNoCase( "^([\w\-\.]+(?:\@(?!stable\b)(?!be\b)[a-zA-Z][\w\-]*)?)(?:\@(.+))?$", package, 1, true ); if ( matches.pos.len() >= 3 && matches.pos[ 3 ] != 0 ) { // Note this can also be a semver range like 1.2.x, >2.0.0, or 1.0.4-2.x // For now I'm assuming it's a specific version @@ -441,17 +442,30 @@ component accessors="true" implements="IEndpointInteractive" { // Test package location to see what endpoint we can refer to. var endpointData = endpointService.resolveEndpoint( downloadURL, 'fakePath' ); - - job.addLog( "Deferring to [#endpointData.endpointName#] endpoint for #getNamePrefixes()# entry [#slug#]..." ); - - var packagePath = endpointData.endpoint.resolvePackage( endpointData.package, arguments.verbose ); - - // Cheat for people who set a version, slug, or type in ForgeBox, but didn't put it in their box.json - var boxJSON = packageService.readPackageDescriptorRaw( packagePath ); - if( !structKeyExists( boxJSON, 'type' ) || !len( boxJSON.type ) ) { boxJSON.type = entryData.typeslug; } - if( !structKeyExists( boxJSON, 'slug' ) || !len( boxJSON.slug ) ) { boxJSON.slug = entryData.slug; } - if( !structKeyExists( boxJSON, 'version' ) || !len( boxJSON.version ) ) { boxJSON.version = version; } - packageService.writePackageDescriptor( boxJSON, packagePath ); + + // Very simple check for HTTP URLs pointing to a Lex file + if( isInstanceOf( endpointData.endpoint, 'HTTP' ) && entryData.typeslug == 'lucee-extensions' ) { + job.addLog( "Deferring to [Lex] endpoint for #getNamePrefixes()# entry [#slug#]..." ); + var packagePath = lexEndpoint.resolvePackage( downloadURL ); + + var boxJSON = packageService.readPackageDescriptorRaw( packagePath ); + boxJSON.slug = entryData.slug; + boxJSON.name = entryData.title; + boxJSON.version = version; + packageService.writePackageDescriptor( boxJSON, packagePath ); + + } else { + job.addLog( "Deferring to [#endpointData.endpointName#] endpoint for #getNamePrefixes()# entry [#slug#]..." ); + var packagePath = endpointData.endpoint.resolvePackage( endpointData.package, arguments.verbose ); + + // Cheat for people who set a version, slug, or type in ForgeBox, but didn't put it in their box.json + var boxJSON = packageService.readPackageDescriptorRaw( packagePath ); + if( !structKeyExists( boxJSON, 'type' ) || !len( boxJSON.type ) ) { boxJSON.type = entryData.typeslug; } + if( !structKeyExists( boxJSON, 'slug' ) || !len( boxJSON.slug ) ) { boxJSON.slug = entryData.slug; } + if( !structKeyExists( boxJSON, 'version' ) || !len( boxJSON.version ) ) { boxJSON.version = version; } + packageService.writePackageDescriptor( boxJSON, packagePath ); + + } job.addLog( "Storing download in artifact cache..." ); diff --git a/src/cfml/system/endpoints/Lex.cfc b/src/cfml/system/endpoints/Lex.cfc new file mode 100644 index 000000000..f6ba7e5ec --- /dev/null +++ b/src/cfml/system/endpoints/Lex.cfc @@ -0,0 +1,112 @@ +/** +********************************************************************************* +* Copyright Since 2014 CommandBox by Ortus Solutions, Corp +* www.coldbox.org | www.ortussolutions.com +******************************************************************************** +* @author Brad Wood, Luis Majano, Denny Valliant +* +* I am the Lex endpoint. I get Lucee Extensions from an HTTP URL. +* I will spoof a package around the lex so CommandBox doesn't try to unzip the lex itself. +*/ +component accessors=true implements="IEndpoint" singleton { + + // DI + property name="tempDir" inject="tempDir@constants"; + property name="progressableDownloader" inject="ProgressableDownloader"; + property name="progressBar" inject="ProgressBar"; + property name="CR" inject="CR@constants"; + property name='JSONService' inject='JSONService'; + property name='wirebox' inject='wirebox'; + property name='S3Service' inject='S3Service'; + + // Properties + property name="namePrefixes" type="string"; + + function init() { + setNamePrefixes( 'lex' ); + return this; + } + + public string function resolvePackage( required string package, boolean verbose=false ) { + var job = wirebox.getInstance( 'interactiveJob' ); + var folderName = tempDir & '/' & 'temp#createUUID()#'; + var fullLexPath = folderName & '/' & getDefaultName( package ) & '.lex'; + var fullBoxJSONPath = folderName & '/box.json'; + directoryCreate( folderName ); + + job.addLog( "Downloading [#package#]" ); + + var packageUrl = package.startsWith('s3://') ? S3Service.generateSignedURL(package, verbose) : package; + + try { + // Download File + var result = progressableDownloader.download( + packageUrl, // URL to package + fullLexPath, // Place to store it locally + function( status ) { + progressBar.update( argumentCollection = status ); + }, + function( newURL ) { + job.addLog( "Redirecting to: '#arguments.newURL#'..." ); + } + ); + } catch( UserInterruptException var e ) { + directoryDelete( folderName, true ); + rethrow; + } catch( Any var e ) { + directoryDelete( folderName, true ); + throw( '#e.message##CR##e.detail#', 'endpointException' ); + }; + + + // Spoof a box.json so this looks like a package + var boxJSON = { + 'name' : '#getDefaultName( package )#.lex', + 'slug' : getDefaultName( package ), + 'version' : '0.0.0', + 'location' : 'lex:#package#', + 'type' : 'lucee-extensions' + }; + JSONService.writeJSONFile( fullBoxJSONPath, boxJSON ); + + // Here is where our alleged so-called "package" lives. + return folderName; + + } + + public function getDefaultName( required string package ) { + + // Strip protocol and host to reveal just path and query string + package = package.reReplaceNoCase( '^([\w:]+)?//.*?/', '' ); + + // Check and see if the name of the lex appears somewhere in the URL and use that as the pacakge name + // https://search.maven.org/remotecontent?filepath=jline/jline/3.0.0.M1/jline-3.0.0.M1.lex + // https://site.com/path/to/package-1.0.0.lex + + // If we see /foo.lex or name=foo.lex or ?foo.lex + if( package.reFindNoCase( '[/\?=](.*\.lex)' ) ) { + // Then strip the name and remove extension + // Note the first .* is greedy so in the case of + // https://site.com/path/to/file.lex?name=custom.lex + // the regex will extract the last match, i.e. "custom" + return package.reReplaceNoCase( '.*[/\?=](.*\.lex).*', '\1' ).left( -4 ); + } + + // We give up, so just make the entire URL a slug + return reReplaceNoCase( package, '[^a-zA-Z0-9]', '', 'all' ); + } + + public function getUpdate( required string package, required string version, boolean verbose=false ) { + var result = { + // lexs with a semver in the name are considered to not have an update since we assume they are an exact version + isOutdated = !package + .reReplaceNoCase( '^([\w:]+)?//', '' ) + .listRest( '/\' ) + .reFindNoCase( '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' ), + version = 'unknown' + }; + + return result; + } + +} diff --git a/src/cfml/system/services/PackageService.cfc b/src/cfml/system/services/PackageService.cfc index be31d2f4b..61d709eae 100644 --- a/src/cfml/system/services/PackageService.cfc +++ b/src/cfml/system/services/PackageService.cfc @@ -28,7 +28,8 @@ component accessors="true" singleton { property name='systemSettings' inject='SystemSettings'; property name='wirebox' inject='wirebox'; property name="tempDir" inject="tempDir@constants"; - + property name="serverService" inject="serverService"; + /** * Constructor */ @@ -118,14 +119,16 @@ component accessors="true" singleton { job.addErrorLog( "box.json is missing so this isn't really a package! I'll install it anyway, but I'm not happy about it" ); job.addWarnLog( "I'm just guessing what the package name, version and type are. Please ask the package owner to add a box.json." ); var packageType = 'project'; - if( defaultName.len() ) { - job.addWarnLog( "Package name guessed to be [#defaultName#] from your box.json." ); - var packageName = defaultName; - } else { - var packageName = endpointData.endpoint.getDefaultName( endpointData.package ); - } + var packageName = endpointData.endpoint.getDefaultName( endpointData.package ); var version = '1.0.0'; } + + // If the dependency struct in box.json has a name, use it. This is mostly for + // HTTP, Jar, and Lex endpoints to be able to override their package name. + if( defaultName.len() && packageName != defaultName ) { + job.addWarnLog( "Package named [#defaultName#] from your box.json." ); + packageName = defaultName; + } /******************************************************************************************************************/ @@ -343,6 +346,28 @@ component accessors="true" singleton { // This is a jar. } else if( packageType == 'jars' ) { installDirectory = arguments.packagePathRequestingInstallation & '/lib'; + } else if( packageType == 'lucee-extensions' ) { + // This is making several assumption, but if the directory of the installation is a Lucee server, then + // assume the user wants this lex to be dropped in their server context's deploy folder. To override this + // behavior, speciofy a custom install directory in your box.json to in the "install" params. + var serverDetails = serverService.resolveServerDetails( { directory = arguments.packagePathRequestingInstallation } ); + var serverInfo = serverDetails.serverInfo; + + if( !serverDetails.serverIsNew && serverInfo.engineName == 'lucee' && len( serverInfo.serverConfigDir ) ) { + var serverDeployFolder = serverInfo.serverConfigDir & '/lucee-server/deploy/'; + // Handle paths relative to the server home dir + if( serverDeployFolder.uCase().startsWith('/WEB-INF' ) ) { + serverDeployFolder = serverInfo.serverHomeDirectory & serverDeployFolder; + } + if( directoryExists( serverDeployFolder ) ) { + job.addWarnLog( "Current dir seems to be a Lucee server." ); + job.addWarnLog( "Defaulting lex Install to [#serverDeployFolder#]" ); + installDirectory = serverDeployFolder; + artifactDescriptor.createPackageDirectory = false; + ignorePatterns.append( '/box.json' ); + } + } + } } @@ -750,7 +775,7 @@ component accessors="true" singleton { * @version Version of the dependency * @installDirectory The location that the package is installed to including the container folder. * @installDirectoryIsDedicated True if the package was placed in a dedicated folder - * @dev True if this is a development depenency, false if it is a production dependency + * @dev True if this is a development dependency, false if it is a production dependency * * @returns boolean True if box.json was updated, false if update wasn't neccessary (keys already existed with correct values) */ @@ -863,7 +888,7 @@ component accessors="true" singleton { * Removes a dependency from a packge if it exists * @directory The directory that is the root of the package * @packageName Package to add a a dependency - * @dev True if this is a development depenency, false if it is a production dependency + * @dev True if this is a development dependency, false if it is a production dependency */ public function removeDependency( required string directory, required string packageName ) { // Get box.json, create empty if it doesn't exist @@ -1230,7 +1255,7 @@ component accessors="true" singleton { * @package The full endpointID like foo@1.0.0 */ private function parseSlug( required string package ) { - var matches = REFindNoCase( "^([a-zA-Z][\w\-\.]*(?:\@(?!stable\b)(?!be\b)[a-zA-Z][\w\-]*)?)(?:\@(.+))?$", package, 1, true ); + var matches = REFindNoCase( "^([\w\-\.]+(?:\@(?!stable\b)(?!be\b)[a-zA-Z][\w\-]*)?)(?:\@(.+))?$", package, 1, true ); if ( arrayLen( matches.len ) < 2 ) { throw( type = "endpointException", @@ -1247,7 +1272,7 @@ component accessors="true" singleton { private function parseVersion( required string package ) { var version = ''; // foo@1.0.0 - var matches = REFindNoCase( "^([a-zA-Z][\w\-\.]*(?:\@(?!stable\b)(?!be\b)[a-zA-Z][\w\-]*)?)(?:\@(.+))?$", package, 1, true ); + var matches = REFindNoCase( "^([\w\-\.]+(?:\@(?!stable\b)(?!be\b)[a-zA-Z][\w\-]*)?)(?:\@(.+))?$", package, 1, true ); if ( matches.pos.len() >= 3 && matches.pos[ 3 ] != 0 ) { // Note this can also be a semver range like 1.2.x, >2.0.0, or 1.0.4-2.x // For now I'm assuming it's a specific version From e9d90fcc5e90f07c70de51d74bbfbbb87dc3501c Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 23 Jan 2020 17:07:59 -0600 Subject: [PATCH 074/102] COMMANDBOX-1089 --- src/cfml/system/services/PackageService.cfc | 2 +- src/cfml/system/util/FileSystem.cfc | 39 +++++++++++++++------ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/cfml/system/services/PackageService.cfc b/src/cfml/system/services/PackageService.cfc index 61d709eae..992d59456 100644 --- a/src/cfml/system/services/PackageService.cfc +++ b/src/cfml/system/services/PackageService.cfc @@ -349,7 +349,7 @@ component accessors="true" singleton { } else if( packageType == 'lucee-extensions' ) { // This is making several assumption, but if the directory of the installation is a Lucee server, then // assume the user wants this lex to be dropped in their server context's deploy folder. To override this - // behavior, speciofy a custom install directory in your box.json to in the "install" params. + // behavior, specify a custom install directory in your box.json or in the "install" params. var serverDetails = serverService.resolveServerDetails( { directory = arguments.packagePathRequestingInstallation } ); var serverInfo = serverDetails.serverInfo; diff --git a/src/cfml/system/util/FileSystem.cfc b/src/cfml/system/util/FileSystem.cfc index 7a64e5857..4091139d0 100644 --- a/src/cfml/system/util/FileSystem.cfc +++ b/src/cfml/system/util/FileSystem.cfc @@ -302,17 +302,32 @@ component accessors="true" singleton { // If one of the folders has a period, we've got to do something special. - // C:/users/brad.development/foo.cfc turns into /C__users_brad_development/foo.cfc + // C:/users/brad.development/foo/bar.cfc turns into /C__users_brad_development/foo/bar.cfc if( getDirectoryFromPath( arguments.absolutePath ) contains '.' ) { var leadingSlash = arguments.absolutePath.startsWith( '/' ); var UNC = arguments.absolutePath.startsWith( '\\' ); - var mappingPath = getDirectoryFromPath( arguments.absolutePath ); - mappingPath = mappingPath.replace( '\', '/', 'all' ); - mappingPath = mappingPath.listChangeDelims( '/', '/' ); - - var mappingName = mappingPath.replace( ':', '_', 'all' ); - mappingName = mappingName.replace( '.', '_', 'all' ); - mappingName = mappingName.replace( '/', '_', 'all' ); + var leftOver = getDirectoryFromPath( arguments.absolutePath ); + leftOver = leftOver.replace( '\', '/', 'all' ); + leftOver = leftOver.listChangeDelims( '/', '/' ); + var mappingPath = ''; + var mappingName = ''; + + // "eat up" the original path until we've consumed the folder containing the dot + while( leftOver contains '.' ) { + // Strip off the first folder and add it to the mapping name + var nextSegmentCleaned = leftOver.listFirst( '/' ) + .replace( ':', '_', 'all' ) + .replace( '.', '_', 'all' ); + mappingName = mappingName.listAppend( nextSegmentCleaned, '_' ); + + // Add the non-escaped version to the matching path + mappingPath = mappingPath.listAppend( leftOver.listFirst( '/' ), '/' ); + + // Reduce the left over path + leftOver = leftOver.listDeleteAt( 1, '/' ) + } + + // Mapping needs to be in format of /mapping_name mappingName = '/' & mappingName; // *nix needs this @@ -320,13 +335,17 @@ component accessors="true" singleton { mappingPath = '/' & mappingPath; } + var nonMappingPart = getFileFromPath( arguments.absolutePath ); + if( leftOver.len() ) { + nonMappingPart = leftOver & '/' & nonMappingPart; + } // UNC network paths if( UNC ) { var mapping = locateUNCMapping( mappingPath ); - return mapping & '/' & getFileFromPath( arguments.absolutePath ); + return mapping & '/' & nonMappingPart; } else { createMapping( mappingName, mappingPath ); - return mappingName & '/' & getFileFromPath( arguments.absolutePath ); + return mappingName & '/' & nonMappingPart; } } From 41fd5062612ecd17ddc722f522e558d216717ccd Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Sat, 25 Jan 2020 14:28:22 -0600 Subject: [PATCH 075/102] COMMANDBOX-1090 --- .../modules_app/task-commands/models/TaskService.cfc | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/cfml/system/modules_app/task-commands/models/TaskService.cfc b/src/cfml/system/modules_app/task-commands/models/TaskService.cfc index c27d28e9f..7a2286509 100644 --- a/src/cfml/system/modules_app/task-commands/models/TaskService.cfc +++ b/src/cfml/system/modules_app/task-commands/models/TaskService.cfc @@ -101,14 +101,15 @@ component singleton accessors=true { } finally { // Set task exit code into the shell if( !isNull( local.returnedExitCode ) && isSimpleValue( local.returnedExitCode ) ) { - shell.setExitCode( val( local.returnedExitCode ) ); + var finalExitCode = val( local.returnedExitCode ); } else { - shell.setExitCode( taskCFC.getExitCode() ); - } + var finalExitCode = taskCFC.getExitCode(); + } + shell.setExitCode( finalExitCode ); } // If the previous Task failed - if( taskCFC.getExitCode() != 0 ) { + if( finalExitCode != 0 ) { if( job.isActive() ) { job.errorRemaining( message ); @@ -116,7 +117,7 @@ component singleton accessors=true { shell.printString( chr( 10 ) ); } - throw( message='Task returned failing exit code (#taskCFC.getExitCode()#)', detail='Failing Task: #taskFile# #target#', type="commandException", errorCode=taskCFC.getExitCode() ); + throw( message='Task returned failing exit code (#finalExitCode#)', detail='Failing Task: #taskFile# #target#', type="commandException", errorCode=finalExitCode ); } From 0c3a159ce3f842d7d671ae5de3951e9987eea9f9 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Sat, 25 Jan 2020 14:32:52 -0600 Subject: [PATCH 076/102] COMMANDBOX-1091 --- .../system/modules_app/task-commands/models/TaskService.cfc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/cfml/system/modules_app/task-commands/models/TaskService.cfc b/src/cfml/system/modules_app/task-commands/models/TaskService.cfc index 7a2286509..838882bf8 100644 --- a/src/cfml/system/modules_app/task-commands/models/TaskService.cfc +++ b/src/cfml/system/modules_app/task-commands/models/TaskService.cfc @@ -117,6 +117,12 @@ component singleton accessors=true { shell.printString( chr( 10 ) ); } + // Dump out anything the task had printed so far + var result = taskCFC.getResult(); + if( len( result ) ){ + shell.printString( result & cr ); + } + throw( message='Task returned failing exit code (#finalExitCode#)', detail='Failing Task: #taskFile# #target#', type="commandException", errorCode=finalExitCode ); } From 73074e0ae53b649088de6224991b32b16e75b98e Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Sat, 25 Jan 2020 15:03:58 -0600 Subject: [PATCH 077/102] COMMANDBOX-1092 --- src/cfml/system/services/CommandService.cfc | 6 ++++++ src/cfml/system/services/PackageService.cfc | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/cfml/system/services/CommandService.cfc b/src/cfml/system/services/CommandService.cfc index fc1657fb6..f9d10abf2 100644 --- a/src/cfml/system/services/CommandService.cfc +++ b/src/cfml/system/services/CommandService.cfc @@ -343,6 +343,12 @@ component accessors="true" singleton { // Run the command var result = commandInfo.commandReference.CFC.run( argumentCollection = parameterInfo.namedParameters ); lastCommandErrored = commandInfo.commandReference.CFC.hasError(); + + // If the previous command failed + var finalExitCode = commandInfo.commandReference.CFC.getExitCode(); + if( finalExitCode != 0 ) { + throw( message='Command returned failing exit code (#finalExitCode#)', detail='Failing Command: #commandInfo.originalLine#', type="commandException", errorCode=finalExitCode ); + } } catch( any e ){ FRTransService.errorTransaction( FRTrans, e.getPageException() ); lastCommandErrored = true; diff --git a/src/cfml/system/services/PackageService.cfc b/src/cfml/system/services/PackageService.cfc index 992d59456..2559e9d77 100644 --- a/src/cfml/system/services/PackageService.cfc +++ b/src/cfml/system/services/PackageService.cfc @@ -1217,7 +1217,6 @@ component accessors="true" singleton { * @directory The package root */ function runScript( required string scriptName, string directory=shell.pwd(), boolean ignoreMissing=true, interceptData={} ) { - // Read the box.json from this package (if it exists) var boxJSON = readPackageDescriptorRaw( arguments.directory ); // If there is a scripts object with a matching key for this interceptor.... From e73784fee2e20d2d52bebff9b27330380a5475c5 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 27 Jan 2020 12:00:33 -0600 Subject: [PATCH 078/102] Escape semicolons again in JVM args --- src/cfml/system/services/ServerService.cfc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 7c35a2a3a..74f2055a2 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -1139,7 +1139,9 @@ component accessors="true" singleton { // If background, wrap up JVM args to pass through to background servers. "Real" JVM args must come before Runwar args if( background ) { - var argString = argTokens.toList( ';' ); + // Escape any semi colons in the args so Runwar can process this properly + // -Darg=one;-Darg=two + var argString = argTokens.map( ( token ) => token.replace( ';', '\;', 'all' ) ).toList( ';' ); if( len( argString ) ) { args.append( '--jvm-args=#trim( argString )#' ); } From fffe4e9b60b505c3b808313b4ee93b124d972d72 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Sat, 1 Feb 2020 15:59:56 -0600 Subject: [PATCH 079/102] COMMANDBOX-1095 --- .../system/_wirebox/system/aop/Matcher.cfc | 310 ++++ .../_wirebox/system/aop/MethodInterceptor.cfc | 16 + .../_wirebox/system/aop/MethodInvocation.cfc | 128 ++ src/cfml/system/_wirebox/system/aop/Mixer.cfc | 420 ++++++ .../system/_wirebox/system/aop/MixerUtil.cfc | 93 ++ .../system/aop/aspects/CFTransaction.cfc | 67 + .../system/aop/aspects/MethodLogger.cfc | 53 + .../system/aop/tmp/generationPath.txt | 0 .../system/cache/AbstractCacheBoxProvider.cfc | 140 ++ .../_wirebox/system/cache/CacheFactory.cfc | 687 +++++++++ .../_wirebox/system/cache/ICacheProvider.cfc | 177 +++ .../system/cache/IColdboxApplicationCache.cfc | 67 + .../system/cache/config/CacheBoxConfig.cfc | 286 ++++ .../cache/config/DefaultConfiguration.cfc | 57 + .../_wirebox/system/cache/config/LogBox.cfc | 27 + .../cache/config/samples/Sample.CacheBox.cfc | 73 + .../system/_wirebox/system/cache/license.txt | 22 + .../cache/policies/AbstractEvictionPolicy.cfc | 107 ++ .../_wirebox/system/cache/policies/FIFO.cfc | 42 + .../system/cache/policies/IEvictionPolicy.cfc | 22 + .../_wirebox/system/cache/policies/LFU.cfc | 43 + .../_wirebox/system/cache/policies/LIFO.cfc | 45 + .../_wirebox/system/cache/policies/LRU.cfc | 43 + .../cache/providers/CFColdBoxProvider.cfc | 105 ++ .../system/cache/providers/CFProvider.cfc | 583 ++++++++ .../providers/CacheBoxColdBoxProvider.cfc | 124 ++ .../cache/providers/CacheBoxProvider.cfc | 816 ++++++++++ .../cache/providers/LuceeColdboxProvider.cfc | 105 ++ .../system/cache/providers/LuceeProvider.cfc | 493 ++++++ .../system/cache/providers/MockProvider.cfc | 238 +++ .../system/cache/providers/cf-lib/CFStats.cfc | 68 + .../cache/providers/lucee-lib/LuceeStats.cfc | 68 + .../cache/providers/lucee-lib/RailoStats.cfc | 0 .../system/_wirebox/system/cache/readme.md | 100 ++ .../system/cache/report/ReportHandler.cfc | 182 +++ .../_wirebox/system/cache/report/monitor.cfm | 108 ++ .../report/skins/default/CacheCharting.cfm | 46 + .../skins/default/CacheContentReport.cfm | 74 + .../cache/report/skins/default/CachePanel.cfm | 99 ++ .../report/skins/default/CacheReport.cfm | 128 ++ .../cache/report/skins/default/cachebox.css | 190 +++ .../cache/report/skins/default/cachebox.js | 134 ++ .../report/skins/default/images/bg-glass.png | Bin 0 -> 131 bytes .../report/skins/default/images/bg-glass2.png | Bin 0 -> 119 bytes .../system/cache/store/BlackholeStore.cfc | 119 ++ .../store/ConcurrentSoftReferenceStore.cfc | 251 ++++ .../system/cache/store/ConcurrentStore.cfc | 213 +++ .../_wirebox/system/cache/store/DiskStore.cfc | 263 ++++ .../system/cache/store/IObjectStore.cfc | 78 + .../_wirebox/system/cache/store/JDBCStore.cfc | 456 ++++++ .../store/indexers/JDBCMetadataIndexer.cfc | 191 +++ .../cache/store/indexers/MetadataIndexer.cfc | 146 ++ .../cache/store/sql/JDBCStore-MSSQL.sql | 81 + .../cache/store/sql/JDBCStore-MySQL.sql | 18 + .../cache/store/sql/JDBCStore-Postgres.sql | 33 + .../_wirebox/system/cache/util/CacheStats.cfc | 131 ++ .../system/cache/util/ElementCleaner.cfc | 188 +++ .../system/cache/util/EventURLFacade.cfc | 107 ++ .../system/cache/util/ICacheStats.cfc | 46 + .../system/core/collections/ScopeStorage.cfc | 140 ++ .../system/core/conversion/DataMarshaller.cfc | 136 ++ .../core/conversion/ObjectMarshaller.cfc | 96 ++ .../system/core/conversion/XMLConverter.cfc | 356 +++++ .../system/core/dynamic/BeanPopulator.cfc | 511 +++++++ .../system/core/dynamic/MixerUtil.cfc | 234 +++ .../_wirebox/system/core/events/EventPool.cfc | 130 ++ .../system/core/events/EventPoolManager.cfc | 253 ++++ .../_wirebox/system/core/util/CFMLEngine.cfc | 69 + .../system/core/util/CFMappingHelper.cfc | 21 + .../system/core/util/LuceeMappingHelper.cfc | 23 + .../system/_wirebox/system/core/util/Util.cfc | 382 +++++ .../system/_wirebox/system/ioc/Builder.cfc | 754 ++++++++++ .../system/_wirebox/system/ioc/IInjector.cfc | 50 + .../system/_wirebox/system/ioc/IProvider.cfc | 13 + .../system/_wirebox/system/ioc/Injector.cfc | 1190 +++++++++++++++ .../system/_wirebox/system/ioc/Provider.cfc | 108 ++ .../system/_wirebox/system/ioc/Scopes.cfc | 45 + src/cfml/system/_wirebox/system/ioc/Types.cfc | 19 + .../_wirebox/system/ioc/config/Binder.cfc | 1109 ++++++++++++++ .../system/ioc/config/DefaultBinder.cfc | 66 + .../_wirebox/system/ioc/config/LogBox.cfc | 33 + .../_wirebox/system/ioc/config/Mapping.cfc | 947 ++++++++++++ .../_wirebox/system/ioc/config/Mixin.cfc | 25 + .../_wirebox/system/ioc/dsl/CacheBoxDSL.cfc | 88 ++ .../_wirebox/system/ioc/dsl/ColdBoxDSL.cfc | 174 +++ .../_wirebox/system/ioc/dsl/IDSLBuilder.cfc | 29 + .../_wirebox/system/ioc/dsl/LogBoxDSL.cfc | 90 ++ .../system/_wirebox/system/ioc/license.txt | 22 + src/cfml/system/_wirebox/system/ioc/readme.md | 95 ++ .../_wirebox/system/ioc/scopes/CFScopes.cfc | 114 ++ .../_wirebox/system/ioc/scopes/CacheBox.cfc | 130 ++ .../_wirebox/system/ioc/scopes/IScope.cfc | 42 + .../_wirebox/system/ioc/scopes/NoScope.cfc | 65 + .../system/ioc/scopes/RequestScope.cfc | 85 ++ .../_wirebox/system/ioc/scopes/Singleton.cfc | 121 ++ .../system/logging/AbstractAppender.cfc | 248 +++ .../system/_wirebox/system/logging/Layout.cfc | 40 + .../system/_wirebox/system/logging/LogBox.cfc | 306 ++++ .../_wirebox/system/logging/LogEvent.cfc | 81 + .../_wirebox/system/logging/LogLevels.cfc | 85 ++ .../system/_wirebox/system/logging/Logger.cfc | 423 ++++++ .../system/logging/appenders/CFAppender.cfc | 83 ++ .../logging/appenders/ConsoleAppender.cfc | 65 + .../system/logging/appenders/DBAppender.cfc | 352 +++++ .../logging/appenders/DummyAppender.cfc | 40 + .../logging/appenders/EmailAppender.cfc | 150 ++ .../system/logging/appenders/FileAppender.cfc | 337 +++++ .../logging/appenders/RollingFileAppender.cfc | 81 + .../logging/appenders/ScopeAppender.cfc | 123 ++ .../logging/appenders/SocketAppender.cfc | 162 ++ .../logging/appenders/TracerAppender.cfc | 59 + .../system/logging/config/LogBoxConfig.cfc | 421 ++++++ .../logging/config/samples/Sample.LogBox.cfc | 41 + .../_wirebox/system/logging/license.txt | 22 + .../system/_wirebox/system/logging/readme.md | 100 ++ .../system/logging/util/FileRotator.cfc | 88 ++ src/cfml/system/wirebox/box.json | 34 + src/cfml/system/wirebox/system/aop/Mixer.cfc | 53 +- .../system/cache/AbstractCacheBoxProvider.cfc | 592 ++++++-- .../wirebox/system/cache/CacheFactory.cfc | 1294 ++++++++-------- .../wirebox/system/cache/ICacheProvider.cfc | 58 +- .../system/cache/IColdboxApplicationCache.cfc | 14 +- .../system/cache/config/CacheBoxConfig.cfc | 602 ++++---- .../cache/config/samples/Sample.CacheBox.cfc | 30 +- .../cache/policies/AbstractEvictionPolicy.cfc | 75 +- .../wirebox/system/cache/policies/FIFO.cfc | 27 +- .../system/cache/policies/IEvictionPolicy.cfc | 14 +- .../wirebox/system/cache/policies/LFU.cfc | 10 +- .../wirebox/system/cache/policies/LIFO.cfc | 20 +- .../wirebox/system/cache/policies/LRU.cfc | 22 +- .../cache/providers/CFColdBoxProvider.cfc | 214 ++- .../system/cache/providers/CFProvider.cfc | 770 +++++----- .../providers/CacheBoxColdBoxProvider.cfc | 295 ++-- .../cache/providers/CacheBoxProvider.cfc | 1326 ++++++++--------- .../system/cache/providers/ICacheProvider.cfc | 258 ++++ .../cache/providers/IColdBoxProvider.cfc | 101 ++ .../cache/providers/LuceeColdboxProvider.cfc | 218 ++- .../system/cache/providers/LuceeProvider.cfc | 684 ++++----- .../system/cache/providers/MockProvider.cfc | 630 +++++--- .../system/cache/providers/cf-lib/CFStats.cfc | 118 +- .../cache/providers/lucee-lib/LuceeStats.cfc | 84 +- .../system/cache/report/ReportHandler.cfc | 364 ++--- .../wirebox/system/cache/report/monitor.cfm | 4 +- .../system/cache/store/BlackholeStore.cfc | 281 ++-- .../store/ConcurrentSoftReferenceStore.cfc | 479 +++--- .../system/cache/store/ConcurrentStore.cfc | 446 +++--- .../wirebox/system/cache/store/DiskStore.cfc | 530 ++++--- .../system/cache/store/IObjectStore.cfc | 179 ++- .../wirebox/system/cache/store/JDBCStore.cfc | 989 ++++++------ .../store/indexers/JDBCMetadataIndexer.cfc | 394 ++--- .../cache/store/indexers/MetadataIndexer.cfc | 323 ++-- .../wirebox/system/cache/util/CacheStats.cfc | 305 ++-- .../system/cache/util/ElementCleaner.cfc | 64 +- .../system/cache/util/EventURLFacade.cfc | 4 +- .../wirebox/system/cache/util/ICacheStats.cfc | 36 +- .../wirebox/system/cache/util/IStats.cfc | 55 + .../core/collections/ConcurrentProcessor.cfc | 197 +++ .../system/core/collections/ScopeStorage.cfc | 14 +- .../core/conversion/ObjectMarshaller.cfc | 63 +- .../system/core/conversion/XMLConverter.cfc | 8 +- .../system/core/dynamic/BeanPopulator.cfc | 44 +- .../wirebox/system/core/dynamic/MixerUtil.cfc | 38 +- .../wirebox/system/core/events/EventPool.cfc | 7 +- .../system/core/util/CFMappingHelper.cfc | 12 +- .../system/core/util/LuceeMappingHelper.cfc | 14 +- .../system/wirebox/system/core/util/Util.cfc | 35 +- .../system/wirebox/system/ioc/Builder.cfc | 66 +- .../system/wirebox/system/ioc/Injector.cfc | 25 +- .../system/wirebox/system/ioc/Provider.cfc | 2 +- .../wirebox/system/ioc/config/Binder.cfc | 30 +- .../wirebox/system/ioc/dsl/ColdBoxDSL.cfc | 64 +- src/cfml/system/wirebox/system/ioc/readme.md | 2 +- .../wirebox/system/ioc/scopes/CFScopes.cfc | 30 +- .../wirebox/system/ioc/scopes/NoScope.cfc | 20 +- .../system/ioc/scopes/RequestScope.cfc | 24 +- .../wirebox/system/ioc/scopes/Singleton.cfc | 44 +- .../system/logging/AbstractAppender.cfc | 39 +- .../system/wirebox/system/logging/LogBox.cfc | 4 +- .../system/wirebox/system/logging/Logger.cfc | 112 +- .../logging/appenders/ConsoleAppender.cfc | 221 ++- .../system/logging/appenders/FileAppender.cfc | 17 +- .../system/logging/config/LogBoxConfig.cfc | 25 +- .../system/wirebox/system/logging/readme.md | 2 +- 183 files changed, 27491 insertions(+), 5643 deletions(-) create mode 100644 src/cfml/system/_wirebox/system/aop/Matcher.cfc create mode 100644 src/cfml/system/_wirebox/system/aop/MethodInterceptor.cfc create mode 100644 src/cfml/system/_wirebox/system/aop/MethodInvocation.cfc create mode 100644 src/cfml/system/_wirebox/system/aop/Mixer.cfc create mode 100644 src/cfml/system/_wirebox/system/aop/MixerUtil.cfc create mode 100644 src/cfml/system/_wirebox/system/aop/aspects/CFTransaction.cfc create mode 100644 src/cfml/system/_wirebox/system/aop/aspects/MethodLogger.cfc create mode 100644 src/cfml/system/_wirebox/system/aop/tmp/generationPath.txt create mode 100644 src/cfml/system/_wirebox/system/cache/AbstractCacheBoxProvider.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/CacheFactory.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/ICacheProvider.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/IColdboxApplicationCache.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/config/CacheBoxConfig.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/config/DefaultConfiguration.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/config/LogBox.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/config/samples/Sample.CacheBox.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/license.txt create mode 100644 src/cfml/system/_wirebox/system/cache/policies/AbstractEvictionPolicy.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/policies/FIFO.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/policies/IEvictionPolicy.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/policies/LFU.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/policies/LIFO.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/policies/LRU.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/providers/CFColdBoxProvider.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/providers/CFProvider.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/providers/CacheBoxColdBoxProvider.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/providers/CacheBoxProvider.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/providers/LuceeColdboxProvider.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/providers/LuceeProvider.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/providers/MockProvider.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/providers/cf-lib/CFStats.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/providers/lucee-lib/LuceeStats.cfc rename src/cfml/system/{wirebox => _wirebox}/system/cache/providers/lucee-lib/RailoStats.cfc (100%) create mode 100644 src/cfml/system/_wirebox/system/cache/readme.md create mode 100644 src/cfml/system/_wirebox/system/cache/report/ReportHandler.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/report/monitor.cfm create mode 100644 src/cfml/system/_wirebox/system/cache/report/skins/default/CacheCharting.cfm create mode 100644 src/cfml/system/_wirebox/system/cache/report/skins/default/CacheContentReport.cfm create mode 100644 src/cfml/system/_wirebox/system/cache/report/skins/default/CachePanel.cfm create mode 100644 src/cfml/system/_wirebox/system/cache/report/skins/default/CacheReport.cfm create mode 100644 src/cfml/system/_wirebox/system/cache/report/skins/default/cachebox.css create mode 100644 src/cfml/system/_wirebox/system/cache/report/skins/default/cachebox.js create mode 100644 src/cfml/system/_wirebox/system/cache/report/skins/default/images/bg-glass.png create mode 100644 src/cfml/system/_wirebox/system/cache/report/skins/default/images/bg-glass2.png create mode 100644 src/cfml/system/_wirebox/system/cache/store/BlackholeStore.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/store/ConcurrentSoftReferenceStore.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/store/ConcurrentStore.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/store/DiskStore.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/store/IObjectStore.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/store/JDBCStore.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/store/indexers/JDBCMetadataIndexer.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/store/indexers/MetadataIndexer.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/store/sql/JDBCStore-MSSQL.sql create mode 100644 src/cfml/system/_wirebox/system/cache/store/sql/JDBCStore-MySQL.sql create mode 100644 src/cfml/system/_wirebox/system/cache/store/sql/JDBCStore-Postgres.sql create mode 100644 src/cfml/system/_wirebox/system/cache/util/CacheStats.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/util/ElementCleaner.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/util/EventURLFacade.cfc create mode 100644 src/cfml/system/_wirebox/system/cache/util/ICacheStats.cfc create mode 100644 src/cfml/system/_wirebox/system/core/collections/ScopeStorage.cfc create mode 100644 src/cfml/system/_wirebox/system/core/conversion/DataMarshaller.cfc create mode 100644 src/cfml/system/_wirebox/system/core/conversion/ObjectMarshaller.cfc create mode 100644 src/cfml/system/_wirebox/system/core/conversion/XMLConverter.cfc create mode 100644 src/cfml/system/_wirebox/system/core/dynamic/BeanPopulator.cfc create mode 100644 src/cfml/system/_wirebox/system/core/dynamic/MixerUtil.cfc create mode 100644 src/cfml/system/_wirebox/system/core/events/EventPool.cfc create mode 100644 src/cfml/system/_wirebox/system/core/events/EventPoolManager.cfc create mode 100644 src/cfml/system/_wirebox/system/core/util/CFMLEngine.cfc create mode 100644 src/cfml/system/_wirebox/system/core/util/CFMappingHelper.cfc create mode 100644 src/cfml/system/_wirebox/system/core/util/LuceeMappingHelper.cfc create mode 100644 src/cfml/system/_wirebox/system/core/util/Util.cfc create mode 100644 src/cfml/system/_wirebox/system/ioc/Builder.cfc create mode 100644 src/cfml/system/_wirebox/system/ioc/IInjector.cfc create mode 100644 src/cfml/system/_wirebox/system/ioc/IProvider.cfc create mode 100644 src/cfml/system/_wirebox/system/ioc/Injector.cfc create mode 100644 src/cfml/system/_wirebox/system/ioc/Provider.cfc create mode 100644 src/cfml/system/_wirebox/system/ioc/Scopes.cfc create mode 100644 src/cfml/system/_wirebox/system/ioc/Types.cfc create mode 100644 src/cfml/system/_wirebox/system/ioc/config/Binder.cfc create mode 100644 src/cfml/system/_wirebox/system/ioc/config/DefaultBinder.cfc create mode 100644 src/cfml/system/_wirebox/system/ioc/config/LogBox.cfc create mode 100644 src/cfml/system/_wirebox/system/ioc/config/Mapping.cfc create mode 100644 src/cfml/system/_wirebox/system/ioc/config/Mixin.cfc create mode 100644 src/cfml/system/_wirebox/system/ioc/dsl/CacheBoxDSL.cfc create mode 100644 src/cfml/system/_wirebox/system/ioc/dsl/ColdBoxDSL.cfc create mode 100644 src/cfml/system/_wirebox/system/ioc/dsl/IDSLBuilder.cfc create mode 100644 src/cfml/system/_wirebox/system/ioc/dsl/LogBoxDSL.cfc create mode 100644 src/cfml/system/_wirebox/system/ioc/license.txt create mode 100644 src/cfml/system/_wirebox/system/ioc/readme.md create mode 100644 src/cfml/system/_wirebox/system/ioc/scopes/CFScopes.cfc create mode 100644 src/cfml/system/_wirebox/system/ioc/scopes/CacheBox.cfc create mode 100644 src/cfml/system/_wirebox/system/ioc/scopes/IScope.cfc create mode 100644 src/cfml/system/_wirebox/system/ioc/scopes/NoScope.cfc create mode 100644 src/cfml/system/_wirebox/system/ioc/scopes/RequestScope.cfc create mode 100644 src/cfml/system/_wirebox/system/ioc/scopes/Singleton.cfc create mode 100644 src/cfml/system/_wirebox/system/logging/AbstractAppender.cfc create mode 100644 src/cfml/system/_wirebox/system/logging/Layout.cfc create mode 100644 src/cfml/system/_wirebox/system/logging/LogBox.cfc create mode 100644 src/cfml/system/_wirebox/system/logging/LogEvent.cfc create mode 100644 src/cfml/system/_wirebox/system/logging/LogLevels.cfc create mode 100644 src/cfml/system/_wirebox/system/logging/Logger.cfc create mode 100644 src/cfml/system/_wirebox/system/logging/appenders/CFAppender.cfc create mode 100644 src/cfml/system/_wirebox/system/logging/appenders/ConsoleAppender.cfc create mode 100644 src/cfml/system/_wirebox/system/logging/appenders/DBAppender.cfc create mode 100644 src/cfml/system/_wirebox/system/logging/appenders/DummyAppender.cfc create mode 100644 src/cfml/system/_wirebox/system/logging/appenders/EmailAppender.cfc create mode 100644 src/cfml/system/_wirebox/system/logging/appenders/FileAppender.cfc create mode 100644 src/cfml/system/_wirebox/system/logging/appenders/RollingFileAppender.cfc create mode 100644 src/cfml/system/_wirebox/system/logging/appenders/ScopeAppender.cfc create mode 100644 src/cfml/system/_wirebox/system/logging/appenders/SocketAppender.cfc create mode 100644 src/cfml/system/_wirebox/system/logging/appenders/TracerAppender.cfc create mode 100644 src/cfml/system/_wirebox/system/logging/config/LogBoxConfig.cfc create mode 100644 src/cfml/system/_wirebox/system/logging/config/samples/Sample.LogBox.cfc create mode 100644 src/cfml/system/_wirebox/system/logging/license.txt create mode 100644 src/cfml/system/_wirebox/system/logging/readme.md create mode 100644 src/cfml/system/_wirebox/system/logging/util/FileRotator.cfc create mode 100644 src/cfml/system/wirebox/box.json create mode 100644 src/cfml/system/wirebox/system/cache/providers/ICacheProvider.cfc create mode 100644 src/cfml/system/wirebox/system/cache/providers/IColdBoxProvider.cfc create mode 100644 src/cfml/system/wirebox/system/cache/util/IStats.cfc create mode 100644 src/cfml/system/wirebox/system/core/collections/ConcurrentProcessor.cfc diff --git a/src/cfml/system/_wirebox/system/aop/Matcher.cfc b/src/cfml/system/_wirebox/system/aop/Matcher.cfc new file mode 100644 index 000000000..e9324b6ee --- /dev/null +++ b/src/cfml/system/_wirebox/system/aop/Matcher.cfc @@ -0,0 +1,310 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* I match class and method names to data in this matcher +*/ +component accessors="true"{ + + /** + * Any matcher + */ + property name="any"; + /** + * Matching returns + */ + property name="returns"; + /** + * Matching annotations + */ + property name="annotation"; + /** + * Matching annotation value + */ + property name="annotationValue"; + /** + * Matching mapping names + */ + property name="mappings"; + /** + * Matching instances + */ + property name="instanceOf"; + /** + * Matchin regex + */ + property name="regex"; + /** + * Mathching method names + */ + property name="methods"; + /** + * And operator + */ + property name="and"; + /** + * OR operator + */ + property name="or"; + + /** + * Constructor + */ + function init(){ + reset(); + return this; + } + + /** + * Reset the matcher memento to defaults + */ + function reset(){ + // prepare instance for this matcher + variables.any = false; + variables.returns = ""; + variables.annotation = ""; + variables.mappings = ""; + variables.instanceOf = ""; + variables.regex = ""; + variables.methods = ""; + + // Aggregators + variables.and = ""; + variables.or = ""; + + return this; + } + + /** + * Get the matcher memento + */ + function getMemento(){ + var props = listToArray( "and,or,any,returns,annotation,annotationValue,mappings,instanceOf,regex,methods" ); + var memento = {}; + for( var thisProp in props ){ + if( structKeyExists( variables, thisProp) ){ + memento[ thisProp ] = variables[ thisProp ]; + } + } + 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 wirebox.system.ioc.config.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 ) ); + } + // OR matcher set? + if( isObject( variables.or ) ){ + return ( results OR variables.or.matchClass( argumentCollection=arguments ) ); + } + + 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 ){ + var results = matchMethodRules( arguments.metadata ); + + // AND matcher set? + 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 ) ); + } + + 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 ){ + // Some metadata defaults + var name = arguments.metadata.name; + var returns = "any"; + + if( structKeyExists( arguments.metadata, "returntype" ) ){ + returns = arguments.metadata.returntype; + } + + // Start with any() + if( variables.any ){ return true; } + // Check explicit methods + if( len( variables.methods ) AND listFindNoCase( variables.methods, name ) ){ + return true; + } + // regex + if( len( variables.regex ) AND reFindNoCase( variables.regex, name ) ){ + return true; + } + // returns + if( len( variables.returns ) AND variables.returns EQ returns ){ + return true; + } + // annotation + if( len( variables.annotation ) AND structKeyExists( arguments.metadata, variables.annotation ) ){ + // No annotation value + 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 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 wirebox.system.ioc.config.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; + } + // Check explicit mappings + if( len( variables.mappings ) AND listFindNoCase( variables.mappings, arguments.mapping.getName() ) ){ + return true; + } + // regex + if( len( variables.regex ) AND reFindNoCase( variables.regex, path ) ){ + return true; + } + // instanceOf + if( len( variables.instanceOf ) AND isInstanceOf( arguments.target,variables.instanceOf ) ){ + return true; + } + // annotation + if( len( variables.annotation ) AND structKeyExists( md, variables.annotation ) ){ + // No annotation value + if( NOT structKeyExists( variables, "annotationValue" ) ){ + return true; + } + + // check annotation value + if( structKeyExists( variables, "annotationValue" ) AND md[ variables.annotation ] EQ variables.annotationValue ){ + return true; + } + } + + return false; + } + + /** + * Match against any method name or class path + */ + 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 ){ + 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 ){ + variables.annotation = arguments.annotation; + // the value of the annotation + if( structKeyExists( arguments, "value" ) ){ + variables.annotationValue = arguments.value; + } + 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 ); + } + 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 ){ + 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 ){ + variables.regex = arguments.regex; + 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 ); + } + variables.methods = arguments.methods; + return this; + } + + /** + * AND this matcher with another matcher + * @matcher The matcher to AND this matcher with + * @matcher.doc_generic wirebox.system.aop.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 wirebox.system.aop.Matcher + */ + function orMatch( required matcher ){ + variables.or = arguments.matcher; + return this; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/aop/MethodInterceptor.cfc b/src/cfml/system/_wirebox/system/aop/MethodInterceptor.cfc new file mode 100644 index 000000000..a27437acd --- /dev/null +++ b/src/cfml/system/_wirebox/system/aop/MethodInterceptor.cfc @@ -0,0 +1,16 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* Our AOP Method Interceptor Interface +*/ +interface{ + + /** + * Invoke an AOP method invocation + * @invocation The invocation object + * @invocation.doc_generic wirebox.system.aop.methodInvocation + */ + function invokeMethod( required invocation ) output="false"; + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/aop/MethodInvocation.cfc b/src/cfml/system/_wirebox/system/aop/MethodInvocation.cfc new file mode 100644 index 000000000..50aca0d94 --- /dev/null +++ b/src/cfml/system/_wirebox/system/aop/MethodInvocation.cfc @@ -0,0 +1,128 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* This object is used when implementing AOP advices. Each advice you create will implement +* a `invokeMethod()` and receive this object as an argument. +* You can then use any of the properties in this object to construct your advice. +* Please see the property documentation below to understand each of the properties. +* Just remember that in order to continue the execution of the Adviced method you will need to call `proceed()` +*/ +component accessors="true"{ + + /** + * The currently executing method + */ + property name="method"; + /** + * The currently executing method arguments + */ + property name="args"; + /** + * The current method metadata + */ + property name="methodMetadata"; + /** + * The target of execution + */ + property name="target"; + /** + * The target shortname + */ + property name="targetName"; + /** + * The target wirebox mapping + */ + property name="targetMapping"; + /** + * The AOP interceptors to execute + */ + property name="interceptors"; + /** + * The current index of execution + */ + property name="interceptorIndex"; + /** + * The number of interceptors applied + */ + property name="interceptorLen"; + + + /** + * Constructor + * @method The method name that was intercepted + * @args The argument collection that was intercepted + * @methodMetadata The method metadata that was intercepted + * @target The target object reference that was intercepted + * @targetName The name of the target wired up + * @targetMapping The target's mapping object reference + * @targetMapping.doc_generic wirebox.system.ioc.config.Mapping + * @interceptors The array of interceptors for this invocation + */ + function init( + required method, + required args, + required methodMetadata, + required target, + required targetName, + required targetMapping, + required interceptors + ){ + + // Method intercepted + variables.method = arguments.method; + // Arguments intercepted + variables.args = arguments.args; + // Method metadata + variables.methodMetadata = deserializeJSON( URLDecode( arguments.methodMetadata ) ); + // Target intercepted + variables.target = arguments.target; + // Target name + variables.targetName = arguments.targetName; + // Target Mapping Reference + variables.targetMapping = arguments.targetMapping; + // Interceptor array chain + variables.interceptors = arguments.interceptors; + // Current index to start execution + variables.interceptorIndex = 1; + // Length of interceptor + variables.interceptorLen = arrayLen( arguments.interceptors ); + + return this; + } + + /** + * Increment the interceptor index pointer + * @return MethodInvocation + */ + function incrementInterceptorIndex(){ + variables.interceptorIndex++; + return this; + } + + /** + * Set args + * @return MethodInvocation + */ + function setArgs( required args ){ + variables.args = arguments.args; + return this; + } + + /** + * Proceed execution of the method invocation + */ + function proceed(){ + // We will now proceed with our interceptor execution chain or regular method pointer call + // execute the next interceptor in the chain + + // Check Current Index against interceptor length + if( variables.interceptorIndex <= variables.interceptorLen ){ + return variables.interceptors[ variables.interceptorIndex ].invokeMethod( this.incrementInterceptorIndex() ); + } + + // If we get here all interceptors have fired and we need to fire the original proxied method + return variables.target.$wbAOPInvokeProxy( method=variables.method, args=variables.args ); + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/aop/Mixer.cfc b/src/cfml/system/_wirebox/system/aop/Mixer.cfc new file mode 100644 index 000000000..f9b7b6ef9 --- /dev/null +++ b/src/cfml/system/_wirebox/system/aop/Mixer.cfc @@ -0,0 +1,420 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* I am a WireBox listener that provides you with AOP capabilities in your objects. +* +* Listener Properties: +* - generationPath:path - The include path used for code generation +* - dictionaryReload:boolean(false) - The flag to always reload aspect dictionary discover information, great for development +*/ +component accessors="true"{ + + /** + * WireBox + */ + property name="injector"; + /** + * WireBox Binder + */ + property name="binder"; + /** + * Logging class + */ + property name="log"; + /** + * Listener properties + */ + property name="properties"; + /** + * Class matching dictionary + */ + property name="classMatchDictionary"; + /** + * Java System + */ + property name="system"; + /** + * Java UUID Helper + */ + property name="uuid"; + /** + * Mixer utility object + */ + property name="mixerUtil"; + /** + * Class identity + */ + property name="classID"; + + /** + * Listener constructor + * @injector + * @properties + */ + function configure( required injector, required properties ){ + // injector reference + variables.injector = arguments.injector; + // Binder Reference + variables.binder = arguments.injector.getBinder(); + // local logger + variables.log = arguments.injector.getLogBox().getLogger( this ); + // listener properties + variables.properties = arguments.properties; + // class matcher dictionary + variables.classMatchDictionary = createObject( "java", "java.util.concurrent.ConcurrentHashMap" ).init(); + // system + variables.system = createObject( "java", "java.lang.System" ); + // uuid helper + variables.uuid = createobject( "java", "java.util.UUID" ); + // mixer util + variables.mixerUtil = new wirebox.system.aop.MixerUtil(); + // class id code + variables.classID = variables.system.identityHashCode( this ); + + // Default Generation Path? + if( NOT structKeyExists( variables.properties, "generationPath" ) ){ + variables.properties.generationPath = "/wirebox/system/aop/tmp"; + } + + // Check if we can write to generation path + if( !getFileInfo( expandPath(variables.properties.generationPath) ).canWrite ){ + throw( message="The AOP generation directory: '#variables.properties.generationPath#' is not writable, cannot continue." ); + } + + // Class Dictionary Reload + if( NOT structKeyExists( variables.properties, "classMatchReload" ) ){ + variables.properties.classMatchReload = false; + } + + return this; + } + + /** + * Executes our AOP mixer after variabless are created and autowired + */ + function afterInstanceAutowire( required interceptData ){ + var mapping = arguments.interceptData.mapping; + var target = arguments.interceptData.target; + + // check if target already mixed, if so just return, nothing else to do or if the mapping is an aspect + if( structKeyExists( target, "$wbAOPMixed" ) OR mapping.isAspect() ){ return; } + + // Setup variables + var mappingName = lcase( mapping.getName() ); + var idCode = variables.system.identityHashCode( target ); + + // Check if incoming mapping name is already class matched? + if( NOT structKeyExists( variables.classMatchDictionary, mappingName ) ){ + // Register this incoming mapping for class aspect matching + buildClassMatchDictionary( target, mapping, idCode ); + } + + // Now, we check if we have any aspects to apply to this class according to class matchers + if( arrayLen( variables.classMatchDictionary[ mappingName ] ) ){ + AOPBuilder( + target = target, + mapping = mapping, + dictionary = variables.classMatchDictionary[ mappingName ], + idCode = idCode + ); + } + } + + /** + * Build an aspect dictionary for incoming target objects + * @target The incoming target + * @mapping The incoming target mapping + * @idCode The incoming target identifier + * + */ + private function buildClassMatchDictionary( required target, required mapping, required idCode ){ + var aspectBindings = variables.binder.getAspectBindings(); + var bindingsLen = arrayLen( aspectBindings ); + var mappingName = lcase( arguments.mapping.getName() ); + var matchedAspects = []; + + lock name="aop.#variables.classID#.cmd.for.#arguments.idCode#" type="exclusive" timeout="30" throwontimeout="true"{ + // check again, double lock + if( NOT structKeyExists( variables.classMatchDictionary, mappingName ) ){ + + // Discover matching for the class via all aspect bindings + for( var x=1; x LTE bindingsLen; x++ ){ + + // class match? If so, add to dictionary of matched aspects + if( aspectBindings[ x ].classes.matchClass( arguments.target, arguments.mapping ) ){ + arrayAppend( matchedAspects, aspectBindings[ x ] ); + } + + }// end for discovery + + // Log + if( variables.log.canDebug() ){ + variables.log.debug( "Aspect class matching dictionary built for mapping: #mappingName#, aspects: #matchedAspects.toString()#" ); + } + + // Store matched dictionary + variables.classMatchDictionary[ mappingName ] = matchedAspects; + + } // end if in dictionary + } // end lock + } + + /** + * Build and weave all necessary advices on an object via method matching + * @target The incoming target + * @mapping The incoming target mapping + * @dictionary The target aspect dictionary + * @idCode The incoming target identifier + * + */ + private function AOPBuilder( + required target, + required mapping, + required dictionary, + required idCode + ){ + lock name="aop.#variables.classID#.weaveAdvice.id.#arguments.idCode#" type="exclusive" timeout="30" throwOnTimeout="true"{ + // check if weaved already + if( structKeyExists( arguments.target, "$wbAOPMixed" ) ){ return; } + + // decorate target with AOP capabilities + decorateAOPTarget( arguments.target, arguments.mapping ); + + // Process methods via metadata and apply aspects if they match + processTargetMethods( + target = arguments.target, + mapping = arguments.mapping, + metadata = arguments.mapping.getObjectMetadata(), + dictionary = arguments.dictionary + ); + + // finalize AOP + arguments.target.$wbAOPMixed = true; + } + } + + /** + * Process target methods for AOP weaving + * @target The incoming target + * @mapping The incoming target mapping + * @metadata The incoming target metadata + * @dictionary The target aspect dictionary + * + */ + private function processTargetMethods( + required target, + required mapping, + required metadata, + required dictionary + ){ + // check if there are functions, else exit + if( NOT structKeyExists( arguments.metadata, "functions" ) ){ + return; + } + + // Get Function info + var functions = arguments.metadata.functions; + var fncLen = arrayLen( functions ); + + for( var x=1; x LTE fncLen; x++ ){ + + // check if function already proxied, if so, skip it + if( structKeyExists( arguments.target.$wbAOPTargets, functions[ x ].name ) ){ continue; } + + // init matched aspects to weave + var matchedMethodAspects = []; + + // function not proxied yet, let's iterate over aspects and see if we can match + for( var y=1; y LTE arrayLen( arguments.dictionary ); y++){ + // does the jointpoint match against aspect methods + if( arguments.dictionary[ y ].methods.matchMethod( functions[ x ] ) ){ + matchedMethodAspects.addAll( arguments.dictionary[ y ].aspects ); + // Debug Info + if ( variables.log.canDebug() ){ + variables.log.debug( "Target: (#arguments.mapping.getName()#) Method:(#functions[ x ].name#) matches aspects #arguments.dictionary[ y ].aspects.toString()#" ); + } + } + } + + // Build the the AOP advisor with the function pointcut and matched aspects? + if( arrayLen( matchedMethodAspects ) ){ + weaveAdvice( + target = arguments.target, + mapping = arguments.mapping, + jointpoint = functions[ x ].name, + jointPointMD= functions[ x ], + aspects = matchedMethodAspects + ); + } + + } + + // Discover inheritance? Recursion + if( structKeyExists( arguments.metadata, "extends" ) ){ + processTargetMethods( + target = arguments.target, + mapping = arguments.mapping, + metadata = arguments.metadata.extends, + dictionary = arguments.dictionary + ); + } + } + + /** + * Weave an advise into a jointpoint + * @target The incoming target + * @mapping The incoming target mapping + * @jointPoint The jointpoint to proxy + * @jointPointMD The jointpoint metdata to proxy + * @aspects The aspects to weave into the jointpoint + * + */ + private function weaveAdvice( + required target, + required mapping, + required jointPoint, + required jointPointMD, + required aspects + ){ + var udfOut = createObject( "java","java.lang.StringBuilder" ).init( '' ); + var lb = "#chr( 13 )##chr( 10 )#"; + var fncMD = { + name = "", + access = "public", + output ="false", + returnType = "any" + }; + var mappingName = arguments.mapping.getName(); + var mdJSON = urlEncodedFormat( serializeJSON( arguments.jointPointMD ) ); + + // MD proxy Defaults + fncMD.name = arguments.jointPointMD.name; + if( structKeyExists( arguments.jointPointMD, "access" ) ){ + fncMD.access = arguments.jointPointMD.access; + } + if( structKeyExists( arguments.jointPointMD, "output" ) ){ + fncMD.output = arguments.jointPointMD.output; + } + if( structKeyExists( arguments.jointPointMD, "returntype" ) ){ + fncMD.returntype = arguments.jointPointMD.returnType; + } + // Create Original Method Proxy Signature + if( fncMD.access eq "public" ){ + udfOut.append( '#lb#' ); + } + + var thisFNC = ' + <:cffunction name="aop_#hash( arguments.jointpoint )#" + access="#fncMD.access#" + output="#fncMD.output#" + returntype="#fncMD.returntype#" + hint="WireBox AOP just rulez!" + > + + // create new method invocation for this execution + var invocation = createObject( "component", "wirebox.system.aop.MethodInvocation" ).init( + method = "#arguments.jointPoint#", + args = arguments, + methodMetadata = "#mdJSON#", + target = this, + targetName = "#mappingName#", + targetMapping = this.$wbAOPTargetMapping, + interceptors = this.$wbAOPTargets[ "#arguments.jointPoint#" ].interceptors + ); + // execute and return + return invocation.proceed(); + + <:/cffunction> + + + + '; + + // Do : replacement, due to inline compilation avoidances + thisFNC = replace( thisFNC, "<:", "<", "all" ); + udfOut.append( thisFNC ); + + // MD5 Content Checks + var codeSignature = hash( udfOUt.toString() ); + var tmpFile = variables.properties.generationPath & "/" & codeSignature & ".cfm"; + var expandedFile = expandPath( tmpFile ); + + try{ + // Write it out to the generation space if it does not exist + if( !fileExists( expandedFile ) ){ + variables.mixerUtil.writeAspect( expandedFile, udfOUt.toString() ); + } + + // Save jointpoint in method targets alongside the interceptors + arguments.target.$wbAOPStoreJointPoint( arguments.jointpoint, buildInterceptors( arguments.aspects) ); + // Remove the old method to proxy it + arguments.target.$wbAOPRemove( arguments.jointpoint ); + // Mix In generated aspect + arguments.target.$wbAOPInclude( tmpFile ); + + // Remove Temp Aspect from disk + //variables.mixerUtil.removeAspect( expandedFile ); + + // debug info + if( variables.log.canDebug() ){ + variables.log.debug( "Target (#mappingName#) weaved with new (#arguments.jointpoint#) method and with the following aspects: #arguments.aspects.toString()#" ); + } + } catch( Any e ){ + // Remove Stub, just in case. + variables.mixerUtil.removeAspect( expandedFile ); + // log it + if( variables.log.canError() ){ + variables.log.error( "Exception mixing in AOP aspect for (#mappingName#): #e.message# #e.detail#", e ); + } + // throw the exception + throw( + message = "Exception mixing in AOP aspect for (#mappingName#)", + detail = e.message & e.detail & e.stacktrace, + type = "WireBox.aop.Mixer.MixinException" + ); + } + } + + /** + * Build out interceptors according to their aspect names + * @aspects The aspects to construct + * + */ + private array function buildInterceptors( required aspects ){ + var interceptors = []; + + // Get aspects from injector and add to our interceptor array + for( var x=1; x lte arrayLen( arguments.aspects ); x++){ + arrayAppend( interceptors, variables.injector.getInstance( arguments.aspects[ x ] ) ); + } + + return interceptors; + } + + /** + * Decorate a target with AOP capabilities + * @target The incoming target + * @mapping The incoming target mapping + * + */ + private function decorateAOPTarget( required target, required mapping ){ + // Create targets struct for method proxing + arguments.target.$wbAOPTargets = {}; + // Mix in the include command + arguments.target.$wbAOPInclude = variables.mixerUtil.$wbAOPInclude; + // Mix in the remove command + arguments.target.$wbAOPRemove = variables.mixerUtil.$wbAOPRemove; + // Mix in store point information + arguments.target.$wbAOPStoreJointPoint = variables.mixerUtil.$wbAOPStoreJointPoint; + // Mix in method proxy execution + arguments.target.$wbAOPInvokeProxy = variables.mixerUtil.$wbAOPInvokeProxy; + // Mix in target mapping for quick references + arguments.target.$wbAOPTargetMapping = arguments.mapping; + // Log it if possible + if( variables.log.canDebug() ){ + variables.log.debug( "AOP Decoration finalized for Mapping: #arguments.mapping.getName()#" ); + } + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/aop/MixerUtil.cfc b/src/cfml/system/_wirebox/system/aop/MixerUtil.cfc new file mode 100644 index 000000000..ffb823681 --- /dev/null +++ b/src/cfml/system/_wirebox/system/aop/MixerUtil.cfc @@ -0,0 +1,93 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* I am an AOP mixer utility method +*/ +component{ + + /** + * Constructor + */ + function init(){ + return this; + } + + /****************************** AOP UTILITY MIXINS ******************************/ + + /** + * Store JointPoint information + * @jointpoint The jointpoint to proxy + * @interceptors The jointpoint interceptors + * + * @return instance + */ + function $wbAOPStoreJointPoint( required jointpoint, required interceptors ){ + this.$wbAOPTargets[ arguments.jointpoint ] = { + udfPointer = variables[ arguments.jointpoint ], + interceptors = arguments.interceptors + }; + return this; + } + + /** + * Invoke a mixed in proxy method + * @method The method to proxy execute + * @args The method args to proxy execute + */ + function $wbAOPInvokeProxy( required method, required args ){ + return this.$wbAOPTargets[ arguments.method ].udfPointer( argumentCollection=arguments.args ); + } + + /** + * Mix in a template on an injected target + * @templatePath The template to mix in + * + * @return Instance + */ + function $wbAOPInclude( required templatePath ){ + include "#arguments.templatePath#"; + return this; + } + + /** + * Remove a method from this target mixin + * @methodName The method to poof away! + * + * @return Instance + */ + function $wbAOPRemove( required methodName ){ + structDelete( this, arguments.methodName ); + structDelete( variables, arguments.methodName ); + return this; + } + + + /****************************** UTILITY Methods ******************************/ + + /** + * Write an aspect to disk + * @genPath The location path + * @code The code to write + * + * @return Instance + */ + function writeAspect( required genPath, required code ){ + fileWrite( arguments.genPath, arguments.code ); + return this; + } + + /** + * Remove an aspect from disk + * @filePath The location path + * + * @return Instance + */ + function removeAspect( required filePath ){ + if( fileExists( arguments.filePath ) ){ + fileDelete( arguments.filePath ); + } + return this; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/aop/aspects/CFTransaction.cfc b/src/cfml/system/_wirebox/system/aop/aspects/CFTransaction.cfc new file mode 100644 index 000000000..5e21e7dc5 --- /dev/null +++ b/src/cfml/system/_wirebox/system/aop/aspects/CFTransaction.cfc @@ -0,0 +1,67 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* A simple ColdFusion transaction Aspect for WireBox +*/ +component implements="wirebox.system.aop.MethodInterceptor" + classMatcher="any" + accessors="true" + methodMatcher="annotatedWith:transactional"{ + + // DI + property name="log" inject="logbox:logger:{this}"; + + /** + * Constructor + */ + function init(){ + return this; + } + + /** + * Invoke an AOP method invocation + * @invocation The invocation object + * @invocation.doc_generic wirebox.system.aop.methodInvocation + */ + function invokeMethod( required invocation ) output="false"{ + var refLocal = {}; + + // Are we already in a transaction? + if( structKeyExists( request, "cbox_aop_transaction" ) ){ + // debug? + if( log.canDebug() ){ + log.debug( "Call to '#arguments.invocation.getTargetName()#.#arguments.invocation.getMethod()#()' already transactioned, just executing it" ); + } + // Just execute and return; + return arguments.invocation.proceed(); + } + + try{ + + transaction{ + // In Transaction + request[ "cbox_aop_transaction" ] = true; + if( log.canDebug() ){ + log.debug( "Call to '#arguments.invocation.getTargetName()#.#arguments.invocation.getMethod()#()' is now transactioned and begins execution" ); + } + // Execute Transactioned method + refLocal.results = arguments.invocation.proceed(); + } + + + } catch( any e ){ + structDelete( request, "cbox_aop_transaction" ); + log.error( "An exception ocurred in the AOPed transactio for target: #arguments.invocation.getTargetName()#, method: #arguments.invocation.getMethod()#: #cfcatch.message# #cfcatch.detail#", cfcatch ); + rethrow; + } + + // remove transaction pointer + structDelete( request, "cbox_aop_transaction" ); + // results to return? + if( structKeyExists( refLocal, "results" ) ){ + return refLocal.results; + } + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/aop/aspects/MethodLogger.cfc b/src/cfml/system/_wirebox/system/aop/aspects/MethodLogger.cfc new file mode 100644 index 000000000..5a058820e --- /dev/null +++ b/src/cfml/system/_wirebox/system/aop/aspects/MethodLogger.cfc @@ -0,0 +1,53 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* A simple interceptor that logs method calls and their results +*/ +component implements="wirebox.system.aop.MethodInterceptor" + accessors="true"{ + + // DI + property name="log" inject="logbox:logger:{this}"; + + /** + * Log results + */ + property name="logResults" type="boolean" default="true"; + + /** + * Constructor + * @logResults Log results or not + */ + function init( boolean logResults=true){ + variables.logResults = arguments.logResults; + return this; + } + + /** + * Invoke an AOP method invocation + * @invocation The invocation object + * @invocation.doc_generic wirebox.system.aop.methodInvocation + */ + function invokeMethod( required invocation ) output="false"{ + var refLocal = {}; + var debugString = "target: #arguments.invocation.getTargetName()#,method: #arguments.invocation.getMethod()#,arguments:#serializeJSON(arguments.invocation.getArgs())#"; + + // log incoming call + if( log.canDebug() ){ + log.debug( debugString ); + } + + // proceed execution + refLocal.results = arguments.invocation.proceed(); + + // result logging and returns + if( structKeyExists( refLocal, "results") ){ + if( variables.logResults and log.canDebug() ){ + log.debug( "#debugString#, results:", refLocal.results ); + } + return refLocal.results; + } + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/aop/tmp/generationPath.txt b/src/cfml/system/_wirebox/system/aop/tmp/generationPath.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/cfml/system/_wirebox/system/cache/AbstractCacheBoxProvider.cfc b/src/cfml/system/_wirebox/system/cache/AbstractCacheBoxProvider.cfc new file mode 100644 index 000000000..fb701ff17 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/AbstractCacheBoxProvider.cfc @@ -0,0 +1,140 @@ + + + + + + + // setup instance + instance = { + // cache provider name + name = "", + // enabled flag + enabled = false, + // reporting flag + reportingEnabled = false, + // stats reference will go here + stats = "", + // configuration structure + configuration = {}, + // cache factory instance + cacheFactory = "", + // event manager instance + eventManager = "", + // cache internal identifier + cacheID = createObject('java','java.lang.System').identityHashCode(this) + }; + return this; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/CacheFactory.cfc b/src/cfml/system/_wirebox/system/cache/CacheFactory.cfc new file mode 100644 index 000000000..11b005c81 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/CacheFactory.cfc @@ -0,0 +1,687 @@ + + + + + + + + + + + + var defaultConfigPath = "wirebox.system.cache.config.DefaultConfiguration"; + + // Prepare factory instance + instance = { + // CacheBox Factory UniqueID + factoryID = createObject('java','java.lang.System').identityHashCode(this), + // Version + version = "5.1.4+741", + // Configuration object + config = "", + // ColdBox Application Link + coldbox = "", + // Event Manager Link + eventManager = "", + // Configured Event States + eventStates = [ + "afterCacheElementInsert", + "afterCacheElementRemoved", + "afterCacheElementExpired", + "afterCacheElementUpdated", + "afterCacheClearAll", + "afterCacheRegistration", + "afterCacheRemoval", + "beforeCacheRemoval", + "beforeCacheReplacement", + "afterCacheFactoryConfiguration", + "beforeCacheFactoryShutdown", + "afterCacheFactoryShutdown", + "beforeCacheShutdown", + "afterCacheShutdown" + ], + // LogBox Links + logBox = "", + log = "", + // Caches + caches = {} + }; + + // Did we send a factoryID in? + if( len(arguments.factoryID) ){ + instance.factoryID = arguments.factoryID; + } + + // Prepare Lock Info + instance.lockName = "CacheFactory.#instance.factoryID#"; + + // Passed in configuration? + if( NOT structKeyExists( arguments, "config" ) ){ + // Create default configuration + arguments.config = createObject( "component", "wirebox.system.cache.config.CacheBoxConfig" ).init( CFCConfigPath=defaultConfigPath ); + } + else if( isSimpleValue( arguments.config ) ){ + arguments.config = createObject( "component", "wirebox.system.cache.config.CacheBoxConfig" ).init( CFCConfigPath=arguments.config ); + } + + // Check if linking ColdBox + if( structKeyExists(arguments, "coldbox") ){ + // Link ColdBox + instance.coldbox = arguments.coldbox; + // link LogBox + instance.logBox = instance.coldbox.getLogBox(); + // Link Event Manager + instance.eventManager = instance.coldbox.getInterceptorService(); + // Link Interception States + instance.coldbox.getInterceptorService().appendInterceptionPoints( arrayToList(instance.eventStates) ); + } + else{ + // Running standalone, so create our own logging first + configureLogBox( arguments.config.getLogBoxConfig() ); + // Running standalone, so create our own event manager + configureEventManager(); + } + + // Configure Logging for the Cache Factory + instance.log = instance.logBox.getLogger( this ); + + // Configure the Cache Factory + configure( arguments.config ); + + return this; + + + + + + + + var listeners = instance.config.getListeners(); + var regLen = arrayLen(listeners); + var x = 1; + var thisListener = ""; + + // iterate and register listeners + for(x=1; x lte regLen; x++){ + // try to create it + try{ + // create it + thisListener = createObject("component", listeners[x].class); + // configure it + thisListener.configure( this, listeners[x].properties); + } + catch(Any e){ + throw(message="Error creating listener: #listeners[x].toString()#", + detail="#e.message# #e.detail# #e.stackTrace#", + type="CacheBox.ListenerCreationException"); + } + + // Now register listener + instance.eventManager.register(thisListener,listeners[x].name); + + } + + + + + + + + var defaultCacheConfig = ""; + var caches = ""; + var key = ""; + var iData = {}; + + + + + // Store config object + instance.config = arguments.config; + // Validate configuration + instance.config.validate(); + // Reset Registries + instance.caches = {}; + + // Register Listeners if not using ColdBox + if( not isObject(instance.coldbox) ){ + registerListeners(); + } + + // Register default cache first + defaultCacheConfig = instance.config.getDefaultCache(); + createCache(name="default",provider=defaultCacheConfig.provider,properties=defaultCacheConfig); + + // Register named caches + caches = instance.config.getCaches(); + for(key in caches){ + createCache(name=key,provider=caches[key].provider,properties=caches[key].properties); + } + + // Scope registrations + if( instance.config.getScopeRegistration().enabled ){ + doScopeRegistration(); + } + + // Announce To Listeners + iData.cacheFactory = this; + instance.eventManager.processState("afterCacheFactoryConfiguration",iData); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + var defaultCacheConfig = instance.config.getDefaultCache(); + + // Check length + if( len(arguments.name) eq 0 ){ + throw(message="Invalid Cache Name", + detail="The name you sent in is invalid as it was blank, please send in a name", + type="CacheFactory.InvalidNameException"); + } + + // Check it does not exist already + if( cacheExists( arguments.name ) ){ + throw(message="Cache #arguments.name# already exists", + detail="Cannot register named cache as it already exists in the registry", + type="CacheFactory.CacheExistsException"); + } + + // Create default cache instance + cache = createCache(name=arguments.name,provider=defaultCacheConfig.provider,properties=defaultCacheConfig); + + // Return it + return cache; + + + + + + + var iData = {}; + var cacheNames = getCacheNames(); + var cacheLen = arraylen(cacheNames); + var cache = ""; + var i = 1; + + // Log startup + if( instance.log.canDebug() ){ + instance.log.debug("Shutdown of cache factory: #getFactoryID()# requested and started."); + } + + // Notify Listeners + iData = {cacheFactory=this}; + instance.eventManager.processState("beforeCacheFactoryShutdown",iData); + + // safely iterate and shutdown caches + for( i=1; i lte cacheLen; i++){ + + // Get cache to shutdown + cache = getCache( cacheNames[i] ); + + // Log it + if( instance.log.canDebug() ){ + instance.log.debug("Shutting down cache: #cacheNames[i]# on factoryID: #getFactoryID()#."); + } + + //process listners + iData = {cache=cache}; + instance.eventManager.processState("beforeCacheShutdown",iData); + + //Shutdown each cache + cache.shutdown(); + + //process listeners + instance.eventManager.processState("afterCacheShutdown",iData); + + // log + if( instance.log.canDebug() ){ + instance.log.debug("Cache: #cacheNames[i]# was shut down on factoryID: #getFactoryID()#."); + } + } + + // Remove all caches + removeAll(); + + // remove scope registration + removeFromScope(); + + // Notify Listeners + iData = {cacheFactory=this}; + instance.eventManager.processState("afterCacheFactoryShutdown",iData); + + // Log shutdown complete + if( instance.log.canDebug() ){ + instance.log.debug("Shutdown of cache factory: #getFactoryID()# completed."); + } + + + + + + + + var iData = {}; + var cache = ""; + var i = 1; + + // Check if cache exists, else exit out + if( NOT cacheExists(arguments.name) ){ + if( instance.log.canWarn() ){ + instance.log.warn("Trying to shutdown #arguments.name#, but that cache does not exist, skipping."); + } + return; + } + + //get Cache + cache = getCache(arguments.name); + + // log it + if( instance.log.canDebug() ){ + instance.log.debug("Shutdown of cache: #arguments.name# requested and started on factoryID: #getFactoryID()#"); + } + + // Notify Listeners + iData = {cache=cache}; + instance.eventManager.processState("beforeCacheShutdown",iData); + + //Shutdown the cache + cache.shutdown(); + + //process listeners + instance.eventManager.processState("afterCacheShutdown",iData); + + // remove cache + removeCache(arguments.name); + + // Log it + if( instance.log.canDebug() ){ + instance.log.debug("Cache: #arguments.name# was shut down and removed on factoryID: #getFactoryID()#."); + } + + + + + + + var scopeInfo = instance.config.getScopeRegistration(); + if( scopeInfo.enabled ){ + createObject("component","wirebox.system.core.collections.ScopeStorage").init().delete(scopeInfo.key, scopeInfo.scope); + } + + + + + + + + + + + + + // double check + if( structKeyExists( instance.caches, arguments.name ) ){ + + //Log + if( instance.log.canDebug() ){ + instance.log.debug("Cache: #arguments.name# asked to be removed from factory: #getFactoryID()#"); + } + + // Retrieve it + cache = instance.caches[ arguments.name ]; + + // Notify listeners here + iData.cache = cache; + instance.eventManager.processState("beforeCacheRemoval",iData); + + // process shutdown + cache.shutdown(); + + // Remove it + structDelete( instance.caches, arguments.name ); + + // Announce it + iData.cache = arguments.name; + instance.eventManager.processState("afterCacheRemoval",iData); + + // Log it + if( instance.log.canDebug() ){ + instance.log.debug("Cache: #arguments.name# removed from factory: #getFactoryID()#"); + } + + return true; + } + + + + + + + + + + + + + + + var cacheNames = getCacheNames(); + var cacheLen = arraylen(cacheNames); + var i = 1; + + if( instance.log.canDebug() ){ + instance.log.debug("Removal of all caches requested on factoryID: #getFactoryID()#"); + } + + for( i=1; i lte cacheLen; i++){ + removeCache( cacheNames[i] ); + } + + if( instance.log.canDebug() ){ + instance.log.debug("All caches removed."); + } + + + + + + + var cacheNames = getCacheNames(); + var cacheLen = arraylen(cacheNames); + var i = 1; + var cache = ""; + + if( instance.log.canDebug() ){ + instance.log.debug("Executing reap on factoryID: #getFactoryID()#"); + } + + for( i=1; i lte cacheLen; i++){ + cache = getCache( cacheNames[i] ); + cache.reap(); + } + + + + + + + + + + + + + + + + + + + + var name = ""; + var iData = {}; + + // determine cache name + if( isObject(arguments.cache) ){ + name = arguments.cache.getName(); + } + else{ + name = arguments.cache; + } + + + + + // Announce to listeners + iData.oldCache = instance.caches[name]; + iData.newCache = arguments.decoratedCache; + instance.eventManager.processState("beforeCacheReplacement",iData); + + // remove old Cache + structDelete( instance.caches, name); + // Replace it + instance.caches[ name ] = arguments.decoratedCache; + + // debugging + if( instance.log.canDebug() ){ + instance.log.debug("Cache #name# replaced with decorated cache: #getMetadata(arguments.decoratedCache).name# on factoryID: #getFactoryID()#"); + } + + + + + + + + + var cacheNames = getCacheNames(); + var cacheLen = arraylen(cacheNames); + var i = 1; + var cache = ""; + + if( instance.log.canDebug() ){ + instance.log.debug("Clearing all registered caches of their content on factoryID: #getFactoryID()#"); + } + + for( i=1; i lte cacheLen; i++){ + cache = getCache( cacheNames[i] ); + cache.clearAll(); + } + + + + + + + var cacheNames = getCacheNames(); + var cacheLen = arraylen(cacheNames); + var i = 1; + var cache = ""; + + if( instance.log.canDebug() ){ + instance.log.debug("Expiring all registered caches of their content on factoryID: #getFactoryID()#"); + } + + for( i=1; i lte cacheLen; i++){ + cache = getCache( cacheNames[i] ); + cache.expireAll(); + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + // Create Cache + var oCache = createObject("component",arguments.provider).init(); + // Register Name + oCache.setName( arguments.name ); + // Link Properties + oCache.setConfiguration( arguments.properties ); + // Register Cache + registerCache( oCache ); + + return oCache; + + + + + + + + + var scopeInfo = instance.config.getScopeRegistration(); + var scopeStorage = createObject("component","wirebox.system.core.collections.ScopeStorage").init(); + // register factory with scope + scopeStorage.put(scopeInfo.key, this, scopeInfo.scope); + + + + + + + + + + + + + + + + + + + + if( NOT structKeyExists(instance.caches, name) ){ + // Link to this CacheFactory + oCache.setCacheFactory( this ); + // Link ColdBox if using it + if( isObject(instance.coldbox) AND structKeyExists(oCache,"setColdBox")){ + oCache.setColdBox( instance.coldbox ); + } + // Link Event Manager + oCache.setEventManager( instance.eventManager ); + // Call Configure it to start the cache up + oCache.configure(); + // Store it + instance.caches[ name ] = oCache; + + // Announce new cache registration now + iData.cache = oCache; + instance.eventManager.processState("afterCacheRegistration",iData); + } + + + + + + + + + + // Create config object + var oConfig = new wirebox.system.logging.config.LogBoxConfig( CFCConfigPath = arguments.configPath ); + // Create LogBox standalone and store it + instance.logBox = new wirebox.system.logging.LogBox( oConfig ); + + return this; + + + + + + + // create event manager + instance.eventManager = createObject("component","wirebox.system.core.events.EventPoolManager").init( instance.eventStates ); + // register the points to listen to + instance.eventManager.appendInterceptionPoints( arrayToList(instance.eventStates) ); + + + + + + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/ICacheProvider.cfc b/src/cfml/system/_wirebox/system/cache/ICacheProvider.cfc new file mode 100644 index 000000000..e17308ed0 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/ICacheProvider.cfc @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/IColdboxApplicationCache.cfc b/src/cfml/system/_wirebox/system/cache/IColdboxApplicationCache.cfc new file mode 100644 index 000000000..aa6314ced --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/IColdboxApplicationCache.cfc @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/config/CacheBoxConfig.cfc b/src/cfml/system/_wirebox/system/cache/config/CacheBoxConfig.cfc new file mode 100644 index 000000000..4b95406be --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/config/CacheBoxConfig.cfc @@ -0,0 +1,286 @@ + + + + + // Utility class + utility = createObject("component","wirebox.system.core.util.Util"); + + // Instance private scope + instance = structnew(); + + // CacheBox Provider Defaults + DEFAULTS = { + logBoxConfig = "wirebox.system.cache.config.LogBox", + cacheBoxProvider = "wirebox.system.cache.providers.CacheBoxProvider", + coldboxAppProvider = "wirebox.system.cache.providers.CacheBoxColdBoxProvider", + scopeRegistration = { + enabled = true, + scope = "application", + key = "cachebox" + } + }; + + // Startup the configuration + reset(); + + + + + + + + var cacheBoxDSL = ""; + + // Test and load via Data CFC Path + if( structKeyExists( arguments, "CFCConfigPath" ) ){ + arguments.CFCConfig = createObject( "component", arguments.CFCConfigPath ); + } + + // Test and load via Data CFC + if( structKeyExists( arguments, "CFCConfig" ) and isObject( arguments.CFCConfig ) ){ + // Decorate our data CFC + arguments.CFCConfig.getPropertyMixin = utility.getMixerUtil().getPropertyMixin; + // Execute the configuration + arguments.CFCConfig.configure(); + // Get Data + cacheBoxDSL = arguments.CFCConfig.getPropertyMixin("cacheBox","variables",structnew()); + // Load the DSL + loadDataDSL( cacheBoxDSL ); + } + + // Just return, most likely programmatic config + return this; + + + + + + + + + + + + + + + + + + + + + + + + + var cacheBoxDSL = arguments.rawDSL; + var key = ""; + + // Is default configuration defined + if( NOT structKeyExists( cacheBoxDSL, "defaultCache" ) ){ + throw("No default cache defined","Please define the 'defaultCache'","CacheBoxConfig.NoDefaultCacheFound"); + } + + // Register Default Cache + defaultCache(argumentCollection=cacheBoxDSL.defaultCache); + + // Register LogBox Configuration + logBoxConfig( variables.DEFAULTS.logBoxConfig ); + if( structKeyExists( cacheBoxDSL, "logBoxConfig") ){ + logBoxConfig(cacheBoxDSL.logBoxConfig); + } + + // Register Server Scope Registration + if( structKeyExists( cacheBoxDSL, "scopeRegistration") ){ + scopeRegistration(argumentCollection=cacheBoxDSL.scopeRegistration); + } + + // Register Caches + if( structKeyExists( cacheBoxDSL, "caches") ){ + for( key in cacheBoxDSL.caches ){ + cacheBoxDSL.caches[key].name = key; + cache(argumentCollection=cacheBoxDSL.caches[key]); + } + } + + // Register listeners + if( structKeyExists( cacheBoxDSL, "listeners") ){ + for(key=1; key lte arrayLen(cacheBoxDSL.listeners); key++ ){ + listener(argumentCollection=cacheBoxDSL.listeners[key]); + } + } + + + + + + + // default cache + instance.defaultCache = {}; + // logBox File + instance.logBoxConfig = ""; + // Named Caches + instance.caches = {}; + // Listeners + instance.listeners = []; + // Scope Registration + instance.scopeRegistration = { + enabled = false, + scope = "server", + key = "cachebox" + }; + + + + + + + + + + + + + + + + + + + + + + + + + + + // Is the default cache defined + if( structIsEmpty(instance.defaultCache) ){ + throw(message="Invalid Configuration. No default cache defined",type="CacheBoxConfig.NoDefaultCacheFound"); + } + + + + + + + + + + instance.scopeRegistration.enabled = arguments.enabled; + instance.scopeRegistration.key = arguments.key; + instance.scopeRegistration.scope = arguments.scope; + + return this; + + + + + + + + + + + + + + + + + + + + + + var cacheConfig = getDefaultCache(); + + // Append all incoming arguments to configuration, just in case using non-default arguments, maybe for stores + structAppend(cacheConfig, arguments); + + // coldbox enabled context + if( structKeyExists(arguments,"coldboxEnabled") AND arguments.coldboxEnabled ){ + cacheConfig.provider = variables.DEFAULTS.coldboxAppProvider; + } + else{ + cacheConfig.provider = variables.DEFAULTS.cacheboxProvider; + } + + return this; + + + + + + + + + + + + + + + instance.caches[arguments.name] = { + provider = arguments.provider, + properties = arguments.properties + }; + return this; + + + + + + + + + + + + + + + + + + + + + + + + + + + // Name check? + if( NOT len(arguments.name) ){ + arguments.name = listLast(arguments.class,"."); + } + // add listener + arrayAppend(instance.listeners, arguments); + + return this; + + + + + + + + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/config/DefaultConfiguration.cfc b/src/cfml/system/_wirebox/system/cache/config/DefaultConfiguration.cfc new file mode 100644 index 000000000..85dd279dc --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/config/DefaultConfiguration.cfc @@ -0,0 +1,57 @@ +/******************************************************************************** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +******************************************************************************** +* The default ColdBox CacheBox configuration object that is used when the cache factory is created by itself +**/ +component{ + + /** + * Configure CacheBox, that's it! + */ + function configure(){ + + // The CacheBox configuration structure DSL + cacheBox = { + // LogBox Configuration file + logBoxConfig = "wirebox.system.cache.config.LogBox", + + // Scope registration, automatically register the cachebox factory instance on any CF scope + // By default it registeres itself on server scope + scopeRegistration = { + enabled = true, + scope = "application", // the cf scope you want + key = "cacheBox" + }, + + // The defaultCache has an implicit name of "default" which is a reserved cache name + // It also has a default provider of cachebox which cannot be changed. + // All timeouts are in minutes + // Please note that each object store could have more configuration properties + defaultCache = { + objectDefaultTimeout = 120, + objectDefaultLastAccessTimeout = 30, + useLastAccessTimeouts = true, + reapFrequency = 2, + freeMemoryPercentageThreshold = 0, + evictionPolicy = "LRU", + evictCount = 1, + maxObjects = 300, + objectStore = "ConcurrentSoftReferenceStore", + // This switches the internal provider from normal cacheBox to coldbox enabled cachebox + coldboxEnabled = false + }, + + // Register all the custom named caches you like here + caches = { + }, + + // Register all event listeners here, they are created in the specified order + listeners = [ + // { class="", name="", properties={} } + ] + + }; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/config/LogBox.cfc b/src/cfml/system/_wirebox/system/cache/config/LogBox.cfc new file mode 100644 index 000000000..e719a6b55 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/config/LogBox.cfc @@ -0,0 +1,27 @@ +/******************************************************************************** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +******************************************************************************** +The logging configuration object for CacheBox Standalone version. +You can make changes here to determine how CacheBox logs information. For more +information about logBox visit: http://wiki.coldbox.org/wiki/LogBox.cfm +**/ +component{ + + /** + * Configure logBox + */ + function configure(){ + variables.logBox = { + // Define Appenders + appenders = { + console = { + class="wirebox.system.logging.appenders.ConsoleAppender" + } + }, + // Root Logger + root = { levelmax="INFO", appenders="*" } + }; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/config/samples/Sample.CacheBox.cfc b/src/cfml/system/_wirebox/system/cache/config/samples/Sample.CacheBox.cfc new file mode 100644 index 000000000..7450d75a9 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/config/samples/Sample.CacheBox.cfc @@ -0,0 +1,73 @@ +/******************************************************************************** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +******************************************************************************** +* The default ColdBox CacheBox configuration object that is used when the cache factory is created by itself +**/ +component{ + + /** + * Configure CacheBox, that's it! + */ + function configure(){ + + // The CacheBox configuration structure DSL + cacheBox = { + // LogBox Configuration file + logBoxConfig = "wirebox.system.cache.config.LogBoxConfig", + + // Scope registration, automatically register the cachebox factory instance on any CF scope + // By default it registeres itself on server scope + scopeRegistration = { + enabled = true, + scope = "server", // server, session + key = "cacheBox" + }, + + // The defaultCache has an implicit name "default" which is a reserved cache name + // It also has a default provider of cachebox which cannot be changed. + // All timeouts are in minutes + defaultCache = { + objectDefaultTimeout = 60, + objectDefaultLastAccessTimeout = 30, + useLastAccessTimeouts = true, + reapFrequency = 2, + freeMemoryPercentageThreshold = 0, + evictionPolicy = "LRU", + evictCount = 1, + maxObjects = 200, + objectStore = "ConcurrentSoftReferenceStore" + }, + + // Register all the custom named caches you like here + caches = { + sampleCache1 = { + provider="wirebox.system.cache.providers.CacheBoxProvider", + properties = { + objectDefaultTimeout="20", + useLastAccessTimeouts="false", + reapFrequency="1", + evictionPolicy="LFU", + evictCount="1", + maxObjects="100", + objectStore="ConcurrentSoftReferenceStore" + } + }, + sampleCache2 = { + provider = "wirebox.system.cache.providers.CacheBoxProvider", + properties = { + maxObjects = 100, + evictionPolicy="FIFO" + } + } + }, + + // Register all event listeners here, they are created in the specified order + listeners = [ + // { class="", name="", properties={} } + ] + + }; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/license.txt b/src/cfml/system/_wirebox/system/cache/license.txt new file mode 100644 index 000000000..3de5291cb --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/license.txt @@ -0,0 +1,22 @@ +******************************************************************************** +Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +www.ortussolutions.com +******************************************************************************** +ColdBox is open source. However, if you use this product please know that it is bound to the following Licence. +If you use ColdBox, please make mention of it in your code or web site or add a Powered By Coldbox icon. + +Apache License, Version 2.0 + +Copyright [2007] [Luis Majano and Ortus Solutions,Corp] + +Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/policies/AbstractEvictionPolicy.cfc b/src/cfml/system/_wirebox/system/cache/policies/AbstractEvictionPolicy.cfc new file mode 100644 index 000000000..379e1fb78 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/policies/AbstractEvictionPolicy.cfc @@ -0,0 +1,107 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* ---- +* This is an AbstractEviction Policy object for usage in a CacheBox provider +* +* @doc_abstract true +*/ +component serializable=false implements="wirebox.system.cache.policies.IEvictionPolicy" accessors="true"{ + + /** + * A logbox logger + */ + property name="logger"; + + /** + * Associated cache provider + */ + property name="cacheProvider"; + + /** + * Constructor + * @cacheProvider The associated cache provider + * @cacheProvider.doc_generic wirebox.system.cache.ICacheProvider + */ + function init( required cacheProvider ){ + // link associated cache + variables.cacheProvider = arguments.cacheProvider; + // setup logger + variables.logger = arguments.cacheProvider.getCacheFactory().getLogBox().getLogger( this ); + + // Debug logging + if( variables.logger.canDebug() ){ + variables.logger.debug( "Policy #getMetadata( this ).name# constructed for cache: #arguments.cacheProvider.getname()#" ); + } + + return this; + } + + + /** + * Execute the eviction policy on the associated cache + */ + public void function execute(){ + throw( "Abstract method!" ); + } + + /** + * Get the Associated Cache Provider of type: wirebox.system.cache.ICacheProvider + * + * @return wirebox.system.cache.ICacheProvider + */ + public any function getAssociatedCache(){ + return variables.cacheProvider; + } + + /****************************************** PRIVATE ************************************************/ + + /** + * Abstract processing of evictions + * @index The array of metadata keys used for processing evictions + */ + private function processEvictions( required any index ){ + var oCacheManager = variables.cacheProvider; + var indexer = oCacheManager.getObjectStore().getIndexer(); + var indexLength = arrayLen(arguments.index); + var x = 1; + var md = ""; + var evictCount = oCacheManager.getConfiguration().evictCount; + var evictedCounter = 0; + + //Loop Through Metadata + for (x=1; x lte indexLength; x=x+1){ + + // verify object in indexer + if( NOT indexer.objectExists( arguments.index[x] ) ){ + continue; + } + md = indexer.getObjectMetadata( arguments.index[x] ); + + // Evict if not already marked for eviction or an eternal object. + if( md.timeout gt 0 AND NOT md.isExpired ){ + + // Evict The Object + oCacheManager.clear( arguments.index[x] ); + + // Record Eviction + oCacheManager.getStats().evictionHit(); + evictedCounter++; + + // Can we break or keep on evicting + if( evictedCounter GTE evictCount ){ + break; + } + } + }//end for loop + } + + /** + * Get utiliy object + * @return wirebox.system.core.util.Util + */ + private function getUtil(){ + return new wirebox.system.core.util.Util(); + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/policies/FIFO.cfc b/src/cfml/system/_wirebox/system/cache/policies/FIFO.cfc new file mode 100644 index 000000000..18fdc4f5b --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/policies/FIFO.cfc @@ -0,0 +1,42 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* ---- +* This is a FIFO eviction Policy meaning that the first object placed on cache +* will be the first one to come out. +* +* More information can be found here: +* http://en.wikipedia.org/wiki/FIFO +*/ +component extends="wirebox.system.cache.policies.AbstractEvictionPolicy"{ + + /** + * Constructor + * @cacheProvider The associated cache provider of type: wirebox.system.cache.ICacheProvider" doc_generic="wirebox.system.cache.ICacheProvider + */ + public FIFO function init ( required any cacheProvider ){ + super.init( arguments.cacheProvider ); + + return this; + } + + /** + * Execute the policy + */ + public void function execute (){ + var index = ""; + + // Get searchable index + try{ + index = getAssociatedCache() + .getObjectStore() + .getIndexer() + .getSortedKeys( "created", "numeric", "asc" ); + // process evictions + processEvictions( index ); + } catch(Any e) { + getLogger().error( "Error sorting via store indexer #e.message# #e.detail# #e.stackTrace#." ); + } + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/policies/IEvictionPolicy.cfc b/src/cfml/system/_wirebox/system/cache/policies/IEvictionPolicy.cfc new file mode 100644 index 000000000..13f745de1 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/policies/IEvictionPolicy.cfc @@ -0,0 +1,22 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* ---- +* +* CacheBox Eviction polify interface +*/ +interface{ + + /** + * Execute the eviction policy on the associated cache + */ + public void function execute(); + + /** + * Get the Associated Cache Provider of type: wirebox.system.cache.ICacheProvider + * + * @return wirebox.system.cache.ICacheProvider + */ + public any function getAssociatedCache(); + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/policies/LFU.cfc b/src/cfml/system/_wirebox/system/cache/policies/LFU.cfc new file mode 100644 index 000000000..5f64e9928 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/policies/LFU.cfc @@ -0,0 +1,43 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.coldbox.org | www.luismajano.com | www.ortussolutions.com +* ---- +* @author original: Luis Majano, cfscript: Ben Koshy +* LFU Eviction Policy Command +* Removes entities from the cache that are used the least. +* More information can be found here: +* http://en.wikipedia.org/wiki/Least_Frequently_Used +*/ +component extends = "wirebox.system.cache.policies.AbstractEvictionPolicy"{ + + /** + * Constructor + * @cacheProvider The associated cache provider of type: wirebox.system.cache.ICacheProvider" doc_generic="wirebox.system.cache.ICacheProvider + */ + public LFU function init( required any cacheProvider ){ + super.init( arguments.cacheProvider ); + + return this; + } + + /** + * Execute the policy + */ + public void function execute(){ + var index = ""; + + // Get searchable index + try { + index = getAssociatedCache() + .getObjectStore() + .getIndexer() + .getSortedKeys( "hits", "numeric", "asc" ); + // process evictions + processEvictions( index ); + } + catch( any e ){ + getLogger().error( "Error sorting via store indexer #e.message# #e.detail# #e.stackTrace#." ); + } + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/policies/LIFO.cfc b/src/cfml/system/_wirebox/system/cache/policies/LIFO.cfc new file mode 100644 index 000000000..a63c52cf2 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/policies/LIFO.cfc @@ -0,0 +1,45 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* ---- +* @author original: Luis Majano, cfscript: Francesco Pepe +* +* This is a LIFO eviction Policy meaning that the first object placed on cache +* will be the last one to come out. This is usually a structure that represents +* a stack. +* +* More information can be found here: +* http://en.wikipedia.org/wiki/FIFO +*/ +component extends="wirebox.system.cache.policies.AbstractEvictionPolicy"{ + + /** + * This is the constructor + * @cacheProvider The associated cache provider of type: wirebox.system.cache.ICacheProvider" doc_generic="wirebox.system.cache.ICacheProvider + */ + public LIFO function init( required any cacheProvider ){ + super.init( arguments.cacheProvider ); + + return this; + } + + /** + * Execute the policy + */ + public void function execute(){ + var index = ""; + + // Get searchable index + try{ + index = getAssociatedCache() + .getObjectStore() + .getIndexer() + .getSortedKeys( "Created", "numeric", "desc" ); + // process evictions + processEvictions( index ); + } catch( Any e ) { + getLogger().error( "Error sorting via store indexer #e.message# #e.detail# #e.stackTrace#." ); + } + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/policies/LRU.cfc b/src/cfml/system/_wirebox/system/cache/policies/LRU.cfc new file mode 100644 index 000000000..bdce1ba61 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/policies/LRU.cfc @@ -0,0 +1,43 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* ---- +* +* This is the LRU or least recently used algorithm for cachebox. +* It basically discards the least recently used items first according to the last accessed date. +* This is also the default algorithm for CacheBox. +* +* For more information visit: http://en.wikipedia.org/wiki/Least_Recently_Used +*/ +component extends="wirebox.system.cache.policies.AbstractEvictionPolicy"{ + + /** + * This is the constructor + * @cacheProvider The associated cache provider of type: wirebox.system.cache.ICacheProvider" doc_generic="wirebox.system.cache.ICacheProvider + */ + public LRU function init( required any cacheProvider ){ + super.init( arguments.cacheProvider ); + + return this; + } + + /** + * Execute the policy + */ + public void function execute(){ + var index = ""; + + // Get searchable index + try{ + index = getAssociatedCache() + .getObjectStore() + .getIndexer() + .getSortedKeys( "LastAccessed", "numeric", "asc" ); + // process evictions + processEvictions( index ); + } catch( Any e ) { + getLogger().error( "Error sorting via store indexer #e.message# #e.detail# #e.stackTrace#." ); + } + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/providers/CFColdBoxProvider.cfc b/src/cfml/system/_wirebox/system/cache/providers/CFColdBoxProvider.cfc new file mode 100644 index 000000000..e278f45bb --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/providers/CFColdBoxProvider.cfc @@ -0,0 +1,105 @@ +/** +******************************************************************************** +Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +www.ortussolutions.com +******************************************************************************** +Author: Luis Majano +Description: + +This CacheBox provider communicates with the built in caches in +the Adobe ColdFusion Engine for ColdBox applications. + +*/ +component serializable="false" extends="wirebox.system.cache.providers.CFProvider" implements="wirebox.system.cache.IColdboxApplicationCache"{ + + CFColdBoxProvider function init() output=false{ + super.init(); + + // Prefixes + this.VIEW_CACHEKEY_PREFIX = "cf_view-"; + this.EVENT_CACHEKEY_PREFIX = "cf_event-"; + + // URL Facade Utility + instance.eventURLFacade = CreateObject("component","wirebox.system.cache.util.EventURLFacade").init(this); + + return this; + } + + // Cache Key prefixes + any function getViewCacheKeyPrefix() output=false{ return this.VIEW_CACHEKEY_PREFIX; } + any function getEventCacheKeyPrefix() output=false{ return this.EVENT_CACHEKEY_PREFIX; } + + // set the coldbox controller + void function setColdbox(required any coldbox) output=false{ + variables.coldbox = arguments.coldbox; + } + + // Get ColdBox + any function getColdbox() output=false{ return coldbox; } + + // Get Event URL Facade Tool + any function getEventURLFacade() output=false{ return instance.eventURLFacade; } + + /** + * Clear all events + */ + void function clearAllEvents(async=false) output=false{ + var threadName = "clearAllEvents_#replace(instance.uuidHelper.randomUUID(),"-","","all")#"; + + // Async? IF so, do checks + if( arguments.async AND NOT instance.utility.inThread() ){ + thread name="#threadName#"{ + instance.elementCleaner.clearAllEvents(); + } + } + else{ + instance.elementCleaner.clearAllEvents(); + } + } + + /** + * Clear all views + */ + void function clearAllViews(async=false) output=false{ + var threadName = "clearAllViews_#replace(instance.uuidHelper.randomUUID(),"-","","all")#"; + + // Async? IF so, do checks + if( arguments.async AND NOT instance.utility.inThread() ){ + thread name="#threadName#"{ + instance.elementCleaner.clearAllViews(); + } + } + else{ + instance.elementCleaner.clearAllViews(); + } + } + + /** + * Clear event + */ + void function clearEvent(required eventsnippet, queryString="") output=false{ + instance.elementCleaner.clearEvent(arguments.eventsnippet,arguments.queryString); + } + + /** + * Clear multiple events + */ + void function clearEventMulti(required eventsnippets,queryString="") output=false{ + instance.elementCleaner.clearEventMulti(arguments.eventsnippets,arguments.queryString); + } + + /** + * Clear view + */ + void function clearView(required viewSnippet) output=false{ + instance.elementCleaner.clearView(arguments.viewSnippet); + } + + /** + * Clear multiple view + */ + void function clearViewMulti(required viewsnippets) output=false{ + instance.elementCleaner.clearView(arguments.viewsnippets); + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/providers/CFProvider.cfc b/src/cfml/system/_wirebox/system/cache/providers/CFProvider.cfc new file mode 100644 index 000000000..d480b8f32 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/providers/CFProvider.cfc @@ -0,0 +1,583 @@ +/** +******************************************************************************** +Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +www.ortussolutions.com +******************************************************************************** +Author: Luis Majano +Description: + +This CacheBox provider communicates with the built in caches in +the Adobe ColdFusion Engine. + +*/ +component serializable="false" implements="wirebox.system.cache.ICacheProvider"{ + + /** + * Constructor + */ + CFProvider function init() output=false{ + // Setup Cache instance + instance = { + // cache name + name = "", + // enabled cache flag + enabled = false, + // reporting enabled flag + reportingEnabled = false, + // configuration structure + configuration = {}, + // cache factory reference + cacheFactory = "", + // event manager reference + eventManager = "", + // reference to underlying data store + store = "", + // internal cache id + cacheID = createObject('java','java.lang.System').identityHashCode(this), + // Element Cleaner Helper + elementCleaner = CreateObject("component","wirebox.system.cache.util.ElementCleaner").init(this), + // Utilities + utility = createObject("component","wirebox.system.core.util.Util"), + // uuid creation helper + uuidHelper = createobject("java", "java.util.UUID") + }; + + // Provider Property Defaults + instance.DEFAULTS = { + cacheName = "object" + }; + + return this; + } + + /** + * get the cache name + */ + any function getName() output=false{ + return instance.name; + } + + /** + * set the cache name + */ + void function setName(required name) output=false{ + instance.name = arguments.name; + } + + /** + * set the event manager + */ + void function setEventManager(required any EventManager) output=false{ + instance.eventManager = arguments.eventManager; + } + + /** + * get the event manager + */ + any function getEventManager() output=false{ + return instance.eventManager; + } + + /** + * get the cache configuration structure + */ + any function getConfiguration() output=false{ + return instance.configuration; + } + + /** + * set the cache configuration structure + */ + void function setConfiguration(required configuration) output=false{ + instance.configuration = arguments.configuration; + } + + /** + * get the associated cache factory + */ + any function getCacheFactory() output=false{ + return instance.cacheFactory; + } + + /** + * Validate the configuration + **/ + private void function validateConfiguration(){ + var cacheConfig = getConfiguration(); + var key = ""; + + // Validate configuration values, if they don't exist, then default them to DEFAULTS + for(key in instance.DEFAULTS){ + if( NOT structKeyExists(cacheConfig, key) OR NOT len(cacheConfig[key]) ){ + cacheConfig[key] = instance.DEFAULTS[key]; + } + } + } + + /** + * configure the cache for operation + */ + void function configure() output=false{ + var config = getConfiguration(); + var props = []; + + lock name="CFProvider.config.#instance.cacheID#" type="exclusive" throwontimeout="true" timeout="20"{ + + // Prepare the logger + instance.logger = getCacheFactory().getLogBox().getLogger( this ); + + if( instance.logger.canDebug() ) + instance.logger.debug("Starting up CFProvider Cache: #getName()# with configuration: #config.toString()#"); + + // Validate the configuration + validateConfiguration(); + + // Merge configurations + var thisCacheName = config.cacheName; + if ( thisCacheName == "object") { + props = cacheGetProperties(); + } + else { + + // this force CF to create the user defined cache if it doesn't exist + get("___invalid___"); + + var cacheConfig = cacheGetSession( thisCacheName, true ).getCacheConfiguration(); + + // apply parameter configurations + if ( structKeyExists( config, "clearOnFlush") ) { + cacheConfig.setClearOnFlush( config.clearOnFlush ); + } + if ( structKeyExists( config, "diskExpiryThreadIntervalSeconds") ) { + cacheConfig.setDiskExpiryThreadIntervalSeconds( config.diskExpiryThreadIntervalSeconds ); + } + if ( structKeyExists( config, "diskPersistent") ) { + cacheConfig.setDiskPersistent( config.diskPersistent ); + } + if ( structKeyExists( config, "diskSpoolBufferSizeMB") ) { + cacheConfig.setDiskSpoolBufferSizeMB( config.diskSpoolBufferSizeMB ); + } + if ( structKeyExists( config, "eternal") ) { + cacheConfig.setEternal( config.eternal ); + } + if ( structKeyExists( config, "maxElementsInMemory") ) { + cacheConfig.setMaxElementsInMemory( config.maxElementsInMemory ); + } + if ( structKeyExists( config, "maxElementsOnDisk") ) { + cacheConfig.setMaxElementsOnDisk( config.maxElementsOnDisk ); + } + if ( structKeyExists( config, "memoryEvictionPolicy") ) { + cacheConfig.setMemoryStoreEvictionPolicy( config.memoryEvictionPolicy ); + } + if ( structKeyExists( config, "overflowToDisk") ) { + cacheConfig.setOverflowToDisk( config.overflowToDisk ); + } + if ( structKeyExists( config, "timeToIdleSeconds") ) { + cacheConfig.setTimeToIdleSeconds( config.timeToIdleSeconds ); + } + if ( structKeyExists( config, "timeToLiveSeconds") ) { + cacheConfig.setTimeToLiveSeconds( config.timeToLiveSeconds ); + } + + props = [{ + "objectType" = config.cacheName + , "clearOnFlush" = cacheConfig.isClearOnFlush() + , "diskExpiryThreadIntervalSeconds" = cacheConfig.getDiskExpiryThreadIntervalSeconds() + , "diskPersistent" = cacheConfig.isDiskPersistent() + , "diskSpoolBufferSizeMB" = cacheConfig.getDiskSpoolBufferSizeMB() + , "eternal" = cacheConfig.isEternal() + , "maxElementsInMemory" = cacheConfig.getMaxElementsInMemory() + , "maxElementsOnDisk" = cacheConfig.getMaxElementsOnDisk() + , "memoryEvictionPolicy" = cacheConfig.getMemoryStoreEvictionPolicy().toString() + , "overflowToDisk" = cacheConfig.isOverflowToDisk() + , "timeToIdleSeconds" = cacheConfig.getTimeToIdleSeconds() + , "timeToLiveSeconds" = cacheConfig.getTimeToLiveSeconds() + }]; + } + + var key = ""; + for(key in props){ + config["ehcache_#key.objectType#"] = key; + } + + // enabled cache + instance.enabled = true; + instance.reportingEnabled = true; + + if( instance.logger.canDebug() ) + instance.logger.debug( "Cache #getName()# started up successfully" ); + } + } + + /** + * shutdown the cache + */ + void function shutdown() output=false{ + if( instance.logger.canDebug() ) + instance.logger.debug( "CFProvider Cache: #getName()# has been shutdown." ); + } + + /* + * Indicates if cache is ready for operation + */ + any function isEnabled() output=false{ + return instance.enabled; + } + + /* + * Indicates if cache is ready for operation + */ + any function isReportingEnabled() output=false{ + return instance.reportingEnabled; + } + + /* + * Indicates if the cache is Terracota clustered + */ + any function isTerracotaClustered(){ + return getObjectStore().isTerracottaClustered(); + } + + /* + * Indicates if the cache node is coherent + */ + any function isNodeCoherent(){ + return getObjectStore().isNodeCoherent(); + } + + /* + * Returns true if the cache is in coherent mode cluster-wide. + */ + any function isClusterCoherent(){ + return getObjectStore().isClusterCoherent(); + } + + /* + * Get the cache statistics object as wirebox.system.cache.util.ICacheStats + * @doc_generic wirebox.system.cache.util.ICacheStats + */ + any function getStats() output=false{ + return CreateObject("component", "wirebox.system.cache.providers.cf-lib.CFStats").init( getObjectStore().getStatistics() ); + } + + /** + * clear the cache stats + */ + void function clearStatistics() output=false{ + getObjectStore().clearStatistics(); + } + + /** + * Returns the ehCache storage session according to configured cache name + */ + any function getObjectStore() output=false{ + // get the cache session according to set name + var thisCacheName = getConfiguration().cacheName; + if ( thisCacheName == "object") { + return cacheGetSession( "object" ); + } else { + return cacheGetSession( getConfiguration().cacheName, true ); + } + } + + /** + * get the cache's metadata report + */ + any function getStoreMetadataReport() output=false{ + var md = {}; + var keys = getKeys(); + var item = ""; + + for(item in keys){ + md[item] = getCachedObjectMetadata(item); + } + + return md; + } + + /** + * Get a key lookup structure where cachebox can build the report on. Ex: [timeout=timeout,lastAccessTimeout=idleTimeout]. It is a way for the visualizer to construct the columns correctly on the reports + */ + any function getStoreMetadataKeyMap() output="false"{ + var keyMap = { + timeout = "timespan", hits = "hitcount", lastAccessTimeout = "idleTime", + created = "createdtime", LastAccessed = "lasthit" + }; + return keymap; + } + + /** + * get all the keys in this provider + */ + any function getKeys() output=false{ + try{ + var thisCacheName = getConfiguration().cacheName; + if ( thisCacheName == "object") { + return cacheGetAllIds(); + } + return cacheGetAllIds( thisCacheName ); + } + catch(Any e){ + instance.logger.error( "Error retrieving all keys from cache: #e.message# #e.detail#", e.stacktrace ); + return [ "Error retrieving keys from cache: #e.message#" ]; + } + } + + /** + * get an object's cached metadata + */ + any function getCachedObjectMetadata(required any objectKey) output=false{ + var thisCacheName = getConfiguration().cacheName; + if ( thisCacheName == "object") { + return cacheGetMetadata( arguments.objectKey ); + } else { + return; + } + } + + /** + * get an item from cache + */ + any function get(required any objectKey) output=false{ + var thisCacheName = getConfiguration().cacheName; + if ( thisCacheName == "object") { + return cacheGet( arguments.objectKey ); + } else { + return cacheGet( arguments.objectKey, thisCacheName ); + } + } + + /** + * Tries to get an object from the cache, if not found, it calls the 'produce' closure to produce the data and cache it + */ + any function getOrSet( + required any objectKey, + required any produce, + any timeout="0", + any lastAccessTimeout="0", + any extra={} + ){ + + var refLocal = { + object = get( arguments.objectKey ) + }; + + // Verify if it exists? if so, return it. + if( structKeyExists( refLocal, "object" ) ){ return refLocal.object; } + + // else, produce it + lock name="CacheBoxProvider.GetOrSet.#instance.cacheID#.#arguments.objectKey#" type="exclusive" timeout="10" throwonTimeout="true"{ + // double lock + refLocal.object = get( arguments.objectKey ); + if( not structKeyExists( refLocal, "object" ) ){ + // produce it + refLocal.object = arguments.produce(); + // store it + set( objectKey=arguments.objectKey, + object=refLocal.object, + timeout=arguments.timeout, + lastAccessTimeout=arguments.lastAccessTimeout, + extra=arguments.extra ); + } + } + + return refLocal.object; + } + + /** + * get an item silently from cache, no stats advised + */ + any function getQuiet(required any objectKey) output=false{ + var element = getObjectStore().getQuiet( ucase(arguments.objectKey) ); + if( NOT isNull(element) ){ + return element.getValue(); + } + } + + /** + * Not implemented by this cache + */ + any function isExpired(required any objectKey) output=false{ + var element = getObjectStore().getQuiet( ucase(arguments.objectKey) ); + if( NOT isNull(element) ){ + return element.isExpired(); + } + return true; + } + + /** + * check if object in cache + */ + any function lookup(required any objectKey) output=false{ + return lookupQuiet(arguments.objectKey); + } + + /** + * check if object in cache with no stats + */ + any function lookupQuiet(required any objectKey) output=false{ + var thisCacheName = getConfiguration().cacheName; + if ( thisCacheName == "object") { + return !isNull ( cacheGet( arguments.objectKey ) ); + } else { + return !isNull ( cacheGet( arguments.objectKey, thisCacheName ) ); + } + } + + /** + * set an object in cache + */ + any function set(required any objectKey, + required any object, + any timeout="0", + any lastAccessTimeout="0", + any extra) output=false{ + + setQuiet(argumentCollection=arguments); + + //ColdBox events + var iData = { + cache = this, + cacheObject = arguments.object, + cacheObjectKey = arguments.objectKey, + cacheObjectTimeout = arguments.timeout, + cacheObjectLastAccessTimeout = arguments.lastAccessTimeout + }; + getEventManager().processState("afterCacheElementInsert",iData); + + return true; + } + + /** + * set an object in cache with no stats + */ + any function setQuiet(required any objectKey, + required any object, + any timeout="0", + any lastAccessTimeout="0", + any extra) output=false{ + + // check if incoming timeout is a timespan or minute to convert to timespan, do also checks if empty strings + if( findnocase("string", arguments.timeout.getClass().getName() ) ){ + if( len(arguments.timeout) ){ arguments.timeout = createTimeSpan(0,0,arguments.timeout,0); } + else{ arguments.timeout = 0; } + } + if( findnocase("string", arguments.lastAccessTimeout.getClass().getName() ) ){ + if( len(arguments.lastAccessTimeout) ){ arguments.lastAccessTimeout = createTimeSpan(0,0,arguments.lastAccessTimeout,0); } + else{ arguments.lastAccessTimeout = 0; } + } + + var thisCacheName = getConfiguration().cacheName; + if ( thisCacheName == "object" ) { + + // if we passed object to the cache put CF would use a user defined custom "object" cache rather than the default + cachePut(arguments.objectKey,arguments.object,arguments.timeout,arguments.lastAccessTimeout); + + } else { + + cachePut(arguments.objectKey,arguments.object,arguments.timeout,arguments.lastAccessTimeout, thisCacheName); + + } + + return true; + } + + /** + * get cache size + */ + any function getSize() output=false{ + return getObjectStore().getSize(); + } + + /** + * Not implemented, let ehCache due its thang! + */ + void function reap() output=false{ + // Not implemented, let ehCache due its thang! + } + + /** + * clear all elements from cache + */ + void function clearAll() output=false{ + var iData = { + cache = this + }; + + getObjectStore().removeAll(); + + // notify listeners + getEventManager().processState("afterCacheClearAll",iData); + } + + /** + * clear an element from cache + */ + any function clear(required any objectKey) output=false{ + + var thisCacheName = getConfiguration().cacheName; + if ( thisCacheName == "object") { + cacheRemove( arguments.objectKey, false ); + } else { + cacheRemove( arguments.objectKey, false, thisCacheName ); + } + + //ColdBox events + var iData = { + cache = this, + cacheObjectKey = arguments.objectKey + }; + getEventManager().processState("afterCacheElementRemoved",iData); + + return true; + } + + /** + * clear with no stats + */ + any function clearQuiet(required any objectKey) output=false{ + getObjectStore().removeQuiet( ucase(arguments.objectKey) ); + return true; + } + + /** + * Clear by key snippet + */ + void function clearByKeySnippet(required keySnippet,regex=false,async=false) output=false{ + var threadName = "clearByKeySnippet_#replace(instance.uuidHelper.randomUUID(),"-","","all")#"; + + // Async? IF so, do checks + if( arguments.async AND NOT instance.utility.inThread() ){ + thread name="#threadName#"{ + instance.elementCleaner.clearByKeySnippet(arguments.keySnippet,arguments.regex); + } + } + else{ + instance.elementCleaner.clearByKeySnippet(arguments.keySnippet,arguments.regex); + } + } + + /** + * not implemented by cache + */ + void function expireAll() output=false{ + // Just try to evict stuff, not a way to expire all elements. + getObjectStore().evictExpiredElements(); + } + + /** + * not implemented by cache + */ + void function expireObject(required any objectKey) output=false{ + //not implemented + } + + /** + * set the associated cache factory + */ + void function setCacheFactory(required any cacheFactory) output=false{ + instance.cacheFactory = arguments.cacheFactory; + } + +} + diff --git a/src/cfml/system/_wirebox/system/cache/providers/CacheBoxColdBoxProvider.cfc b/src/cfml/system/_wirebox/system/cache/providers/CacheBoxColdBoxProvider.cfc new file mode 100644 index 000000000..40923b1f1 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/providers/CacheBoxColdBoxProvider.cfc @@ -0,0 +1,124 @@ + + + + + + // superSize Me + super.init(); + + // Prefixes + this.VIEW_CACHEKEY_PREFIX = "cbox_view-"; + this.EVENT_CACHEKEY_PREFIX = "cbox_event-"; + + // URL Facade Utility + instance.eventURLFacade = CreateObject("component","wirebox.system.cache.util.EventURLFacade").init(this); + + // ColdBox linkage + instance.coldbox = ""; + + return this; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/providers/CacheBoxProvider.cfc b/src/cfml/system/_wirebox/system/cache/providers/CacheBoxProvider.cfc new file mode 100644 index 000000000..0eb3d08c8 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/providers/CacheBoxProvider.cfc @@ -0,0 +1,816 @@ + + + + + + + + // super size me + super.init(); + + // Logger object + instance.logger = ""; + // Runtime Java object + instance.javaRuntime = createObject("java", "java.lang.Runtime"); + // Locking Timeout + instance.lockTimeout = "15"; + // Eviction Policy + instance.evictionPolicy = ""; + // Element Cleaner Helper + instance.elementCleaner = CreateObject("component","wirebox.system.cache.util.ElementCleaner").init(this); + // Utilities + instance.utility = createObject("component","wirebox.system.core.util.Util"); + // UUID Helper + instance.uuidHelper = createobject("java", "java.util.UUID"); + + // CacheBox Provider Property Defaults + instance.DEFAULTS = { + objectDefaultTimeout = 60, + objectDefaultLastAccessTimeout = 30, + useLastAccessTimeouts = true, + reapFrequency = 2, + freeMemoryPercentageThreshold = 0, + evictionPolicy = "LRU", + evictCount = 1, + maxObjects = 200, + objectStore = "ConcurrentStore", + coldboxEnabled = false + }; + + return this; + + + + + + + var cacheConfig = getConfiguration(); + var key = ""; + + // Validate configuration values, if they don't exist, then default them to DEFAULTS + for(key in instance.DEFAULTS){ + if( NOT structKeyExists(cacheConfig, key) OR NOT len(cacheConfig[key]) ){ + cacheConfig[key] = instance.DEFAULTS[key]; + } + } + + + + + + + + + + + + + + // Prepare the logger + instance.logger = getCacheFactory().getLogBox().getLogger( this ); + + if( instance.logger.canDebug() ){ + instance.logger.debug("Starting up CacheBox Cache: #getName()# with configuration: #cacheConfig.toString()#"); + } + + // Validate the configuration + validateConfiguration(); + + // Prepare Statistics + instance.stats = CreateObject("component","wirebox.system.cache.util.CacheStats").init(this); + + // Setup the eviction Policy to use + evictionPolicy = locateEvictionPolicy( cacheConfig.evictionPolicy ); + instance.evictionPolicy = CreateObject("component", evictionPolicy).init(this); + + // Create the object store the configuration mandated + objectStore = locateObjectStore( cacheConfig.objectStore ); + instance.objectStore = CreateObject("component", objectStore).init(this); + + // Enable cache + instance.enabled = true; + // Enable reporting + instance.reportingEnabled = true; + + // startup message + if( instance.logger.canDebug() ){ + instance.logger.debug( "CacheBox Cache: #getName()# has been initialized successfully for operation" ); + } + + + + + + + + + if( instance.logger.canDebug() ) + instance.logger.debug("CacheBox Cache: #getName()# has been shutdown."); + + + + + + + + if( fileExists( expandPath("/wirebox/system/cache/policies/#arguments.policy#.cfc") ) ){ + return "wirebox.system.cache.policies.#arguments.policy#"; + } + return arguments.policy; + + + + + + + + if( fileExists( expandPath("/wirebox/system/cache/store/#arguments.store#.cfc") ) ){ + return "wirebox.system.cache.store.#arguments.store#"; + } + return arguments.store; + + + + + + + + + + + var returnStruct = structnew(); + var x = 1; + var thisKey = ""; + + // Normalize keys + if( isArray(arguments.keys) ){ + arguments.keys = arrayToList( arguments.keys ); + } + + // Loop on Keys + for(x=1;x lte listLen(arguments.keys);x++){ + thisKey = arguments.prefix & listGetAt(arguments.keys,x); + returnStruct[thiskey] = lookup( thisKey ); + } + + return returnStruct; + + + + + + + + if( lookupQuiet(arguments.objectKey) ){ + // record a hit + getStats().hit(); + return true; + } + + // record a miss + getStats().miss(); + + return false; + + + + + + + + // cleanup the key + arguments.objectKey = lcase(arguments.objectKey); + + return instance.objectStore.lookup( arguments.objectKey ); + + + + + + + + var refLocal = {}; + // cleanup the key + arguments.objectKey = lcase( arguments.objectKey ); + + // get quietly + refLocal.results = instance.objectStore.get( arguments.objectKey ); + if( !isNull( refLocal.results ) ){ + getStats().hit(); + return refLocal.results; + } + getStats().miss(); + // don't return anything = null + + + + + + + + + var refLocal = {}; + + // cleanup the key + arguments.objectKey = lcase( arguments.objectKey ); + + // get object from store + refLocal.results = instance.objectStore.getQuiet( arguments.objectKey ); + if( structKeyExists(refLocal, "results") ){ + return refLocal.results; + } + + // don't return anything = null + + + + + + + + + + + var returnStruct = structnew(); + var x = 1; + var thisKey = ""; + + // Normalize keys + if( isArray(arguments.keys) ){ + arguments.keys = arrayToList( arguments.keys ); + } + + // Clear Prefix + arguments.prefix = trim(arguments.prefix); + + // Loop keys + for(x=1;x lte listLen(arguments.keys);x=x+1){ + thisKey = arguments.prefix & listGetAt(arguments.keys,x); + if( lookup(thisKey) ){ + returnStruct[thiskey] = get(thisKey); + } + } + + return returnStruct; + + + + + + + + + + // Cleanup the key + arguments.objectKey = lcase(trim(arguments.objectKey)); + + // Check if in the pool first + if( instance.objectStore.getIndexer().objectExists(arguments.objectKey) ){ + return instance.objectStore.getIndexer().getObjectMetadata(arguments.objectKey); + } + + return structnew(); + + + + + + + + + + + var returnStruct = structnew(); + var x = 1; + var thisKey = ""; + + // Normalize keys + if( isArray(arguments.keys) ){ + arguments.keys = arrayToList( arguments.keys ); + } + + // Clear Prefix + arguments.prefix = trim(arguments.prefix); + + // Loop on Keys + for(x=1;x lte listLen(arguments.keys);x=x+1){ + thisKey = arguments.prefix & listGetAt(arguments.keys,x); + returnStruct[thiskey] = getCachedObjectMetadata(thisKey); + } + + return returnStruct; + + + + + + + + + + + + + var key = 0; + // Clear Prefix + arguments.prefix = trim(arguments.prefix); + // Loop Over mappings + for(key in arguments.mapping){ + // Cache theses puppies + set(objectKey=arguments.prefix & key,object=arguments.mapping[key],timeout=arguments.timeout,lastAccessTimeout=arguments.lastAccessTimeout); + } + + + + + + + + + + + + + + var refLocal = { + object = get( arguments.objectKey ) + }; + // Verify if it exists? if so, return it. + if( structKeyExists( refLocal, "object" ) ){ return refLocal.object; } + // else, produce it + + + + // double lock + refLocal.object = get( arguments.objectKey ); + if( not structKeyExists( refLocal, "object" ) ){ + // produce it + refLocal.object = arguments.produce(); + // store it + set( objectKey=arguments.objectKey, + object=refLocal.object, + timeout=arguments.timeout, + lastAccessTimeout=arguments.lastAccessTimeout, + extra=arguments.extra ); + } + + + + + + + + + + + + + + + + + var iData = ""; + // Check if updating or not + var refLocal = { + oldObject = getQuiet( arguments.objectKey ) + }; + + // save object + setQuiet(arguments.objectKey,arguments.object,arguments.timeout,arguments.lastAccessTimeout); + + // Announce update if it exists? + if( structKeyExists(refLocal,"oldObject") ){ + // interception Data + iData = { + cache = this, + cacheObjectKey = arguments.objectKey, + cacheNewObject = arguments.object, + cacheOldObject = refLocal.oldObject + }; + + // announce it + getEventManager().processState("afterCacheElementUpdated", iData); + } + + // interception Data + iData = { + cache = this, + cacheObject = arguments.object, + cacheObjectKey = arguments.objectKey, + cacheObjectTimeout = arguments.timeout, + cacheObjectLastAccessTimeout = arguments.lastAccessTimeout + }; + + // announce it + getEventManager().processState("afterCacheElementInsert", iData); + + return true; + + + + + + + + + + + + + + var isJVMSafe = true; + var config = getConfiguration(); + var iData = {}; + + // cleanup the key + arguments.objectKey = lcase(arguments.objectKey); + + // JVM Checks + if( config.freeMemoryPercentageThreshold NEQ 0 AND thresholdChecks(config.freeMemoryPercentageThreshold) EQ false){ + // evict some stuff + instance.evictionPolicy.execute(); + } + + // Max objects check + if( config.maxObjects NEQ 0 AND getSize() GTE config.maxObjects ){ + // evict some stuff + instance.evictionPolicy.execute(); + } + + // Provider Default Timeout checks + if( NOT len(arguments.timeout) OR NOT isNumeric(arguments.timeout) ){ + arguments.timeout = config.objectDefaultTimeout; + } + if( NOT len(arguments.lastAccessTimeout) OR NOT isNumeric(arguments.lastAccessTimeout) ){ + arguments.lastAccessTimeout = config.objectDefaultLastAccessTimeout; + } + + // save object + instance.objectStore.set(arguments.objectKey,arguments.object,arguments.timeout,arguments.lastAccessTimeout); + + return true; + + + + + + + + + + + var returnStruct = {}; + var x = 1; + var thisKey = ""; + + // Clear Prefix + arguments.prefix = trim(arguments.prefix); + + // array? + if( isArray(arguments.keys) ){ + arguments.keys = arrayToList( arguments.keys ); + } + + // Loop on Keys + for(x=1;x lte listLen(arguments.keys); x++){ + thisKey = arguments.prefix & listGetAt(arguments.keys,x); + returnStruct[thiskey] = clear(thisKey); + } + + return returnStruct; + + + + + + + + + + + + + + + + + + + + + + + + + + // clean key + arguments.objectKey = lcase(trim(arguments.objectKey)); + + // clear key + return instance.objectStore.clear( arguments.objectKey ); + + + + + + + + var clearCheck = clearQuiet( arguments.objectKey ); + var iData = { + cache = this, + cacheObjectKey = arguments.objectKey + }; + + // If cleared notify listeners + if( clearCheck ){ + getEventManager().processState("afterCacheElementRemoved",iData); + } + + return clearCheck; + + + + + + + var iData = { + cache = this + }; + + instance.objectStore.clearAll(); + + // notify listeners + getEventManager().processState("afterCacheClearAll",iData); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + var keyIndex = 1; + var cacheKeys = ""; + var cacheKeysLen = 0; + var thisKey = ""; + var thisMD = ""; + var config = getConfiguration(); + var sTime = getTickCount(); + + + + + + + // log it + if( instance.logger.canDebug() ) + instance.logger.debug( "Starting to reap CacheBoxProvider: #getName()#, id: #instance.cacheID#" ); + + // Run Storage reaping first, before our local algorithm + instance.objectStore.reap(); + + // Let's Get our reaping vars ready, get a duplicate of the pool metadata so we can work on a good copy + cacheKeys = getKeys(); + cacheKeysLen = ArrayLen(cacheKeys); + + //Loop through keys + for (keyIndex=1; keyIndex LTE cacheKeysLen; keyIndex++){ + + //The Key to check + thisKey = cacheKeys[keyIndex]; + + //Get the key's metadata thread safe. + thisMD = getCachedObjectMetadata(thisKey); + + // Check if found, else continue, already reaped. + if( structIsEmpty(thisMD) ){ continue; } + + //Reap only non-eternal objects + if ( thisMD.timeout GT 0 ){ + + // Check if expired already + if( thisMD.isExpired ){ + // Clear the object from cache + if( clear( thisKey ) ){ + // Announce Expiration only if removed, else maybe another thread cleaned it + announceExpiration(thisKey); + } + continue; + } + + //Check for creation timeouts and clear + if ( dateDiff("n", thisMD.created, now() ) GTE thisMD.timeout ){ + + // Clear the object from cache + if( clear( thisKey ) ){ + // Announce Expiration only if removed, else maybe another thread cleaned it + announceExpiration(thisKey); + } + continue; + } + + //Check for last accessed timeouts. If object has not been accessed in the default span + if ( config.useLastAccessTimeouts AND + dateDiff("n", thisMD.LastAccessed, now() ) gte thisMD.LastAccessTimeout ){ + + // Clear the object from cache + if( clear( thisKey ) ){ + // Announce Expiration only if removed, else maybe another thread cleaned it + announceExpiration(thisKey); + } + continue; + } + }//end timeout gt 0 + + }//end looping over keys + + //Reaping about to end, set new reaping date. + getStats().setLastReapDatetime( now() ); + + // log it + if( instance.logger.canDebug() ) + instance.logger.debug( "Finished reap in #getTickCount()-sTime#ms for CacheBoxProvider: #getName()#, id: #instance.cacheID#" ); + + + + + + + + expireByKeySnippet(keySnippet=".*",regex=true); + + + + + + + + instance.objectStore.expireObject( lcase(trim(arguments.objectKey)) ); + + + + + + + + + + + var keyIndex = 1; + var cacheKeys = getKeys(); + var cacheKeysLen = arrayLen(cacheKeys); + var tester = 0; + + // Loop Through Metadata + for (keyIndex=1; keyIndex LTE cacheKeysLen; keyIndex++){ + + // Using Regex? + if( arguments.regex ){ + tester = reFindnocase(arguments.keySnippet, cacheKeys[keyIndex]); + } + else{ + tester = findnocase(arguments.keySnippet, cacheKeys[keyIndex]); + } + + // Check if object still exists + if( tester + AND instance.objectStore.lookup( cacheKeys[keyIndex] ) + AND getCachedObjectMetadata(cacheKeys[keyIndex]).timeout GT 0){ + + expireObject( cacheKeys[keyIndex] ); + + } + }//end key loops + + + + + + + + + + + + + + + + + + var target = instance.objectStore.getIndexer().getPoolMetadata(); + + return target; + + + + + + + var keyMap = { + timeout = "timeout", hits = "hits", lastAccessTimeout = "lastAccessTimeout", + created = "created", LastAccessed = "LastAccessed", isExpired="isExpired" + }; + return keymap; + + + + + + + + + + + + + + + + + + + + var iData = { + cache = this, + cacheObjectKey = arguments.objectKey + }; + // Execute afterCacheElementExpired Interception + getEventManager().processState("afterCacheElementExpired",iData); + + + + + + + + var check = true; + var jvmThreshold = 0; + + try{ + jvmThreshold = ( (instance.javaRuntime.getRuntime().freeMemory() / instance.javaRuntime.getRuntime().maxMemory() ) * 100 ); + check = arguments.threshold LT jvmThreshold; + } + catch(any e){ + check = true; + } + + return check; + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/providers/LuceeColdboxProvider.cfc b/src/cfml/system/_wirebox/system/cache/providers/LuceeColdboxProvider.cfc new file mode 100644 index 000000000..c018faa55 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/providers/LuceeColdboxProvider.cfc @@ -0,0 +1,105 @@ +/** +******************************************************************************** +Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +www.ortussolutions.com +******************************************************************************** +Author: Luis Majano +Description: + +This CacheBox provider communicates with the built in caches in +the Lucee Engine for ColdBox applications. + +*/ +component serializable="false" extends="wirebox.system.cache.providers.LuceeProvider" implements="wirebox.system.cache.IColdboxApplicationCache"{ + + LuceeColdBoxProvider function init() output=false{ + super.init(); + + // Cache Prefixes + this.VIEW_CACHEKEY_PREFIX = "lucee_view-"; + this.EVENT_CACHEKEY_PREFIX = "lucee_event-"; + + // URL Facade Utility + instance.eventURLFacade = CreateObject("component","wirebox.system.cache.util.EventURLFacade").init(this); + + return this; + } + + // Cache Key prefixes + any function getViewCacheKeyPrefix() output=false{ return this.VIEW_CACHEKEY_PREFIX; } + any function getEventCacheKeyPrefix() output=false{ return this.EVENT_CACHEKEY_PREFIX; } + + // set the coldbox controller + void function setColdbox(required any coldbox) output=false{ + variables.coldbox = arguments.coldbox; + } + + // Get ColdBox + any function getColdbox() output=false{ return coldbox; } + + // Get Event URL Facade Tool + any function getEventURLFacade() output=false{ return instance.eventURLFacade; } + + /** + * Clear all events + */ + void function clearAllEvents(async=false) output=false{ + var threadName = "clearAllEvents_#replace(instance.uuidHelper.randomUUID(),"-","","all")#"; + + // Async? IF so, do checks + if( arguments.async AND NOT instance.utility.inThread() ){ + thread name="#threadName#"{ + instance.elementCleaner.clearAllEvents(); + } + } + else{ + instance.elementCleaner.clearAllEvents(); + } + } + + /** + * Clear all views + */ + void function clearAllViews(async=false) output=false{ + var threadName = "clearAllViews_#replace(instance.uuidHelper.randomUUID(),"-","","all")#"; + + // Async? IF so, do checks + if( arguments.async AND NOT instance.utility.inThread() ){ + thread name="#threadName#"{ + instance.elementCleaner.clearAllViews(); + } + } + else{ + instance.elementCleaner.clearAllViews(); + } + } + + /** + * Clear event + */ + void function clearEvent(required eventsnippet, queryString="") output=false{ + instance.elementCleaner.clearEvent(arguments.eventsnippet,arguments.queryString); + } + + /** + * Clear multiple events + */ + void function clearEventMulti(required eventsnippets,queryString="") output=false{ + instance.elementCleaner.clearEventMulti(arguments.eventsnippets,arguments.queryString); + } + + /** + * Clear view + */ + void function clearView(required viewSnippet) output=false{ + instance.elementCleaner.clearView(arguments.viewSnippet); + } + + /** + * Clear multiple view + */ + void function clearViewMulti(required viewsnippets) output=false{ + instance.elementCleaner.clearView(arguments.viewsnippets); + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/providers/LuceeProvider.cfc b/src/cfml/system/_wirebox/system/cache/providers/LuceeProvider.cfc new file mode 100644 index 000000000..e393cc311 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/providers/LuceeProvider.cfc @@ -0,0 +1,493 @@ +/** +******************************************************************************** +Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +www.ortussolutions.com +******************************************************************************** +Author: Luis Majano +Description: + +This CacheBox provider communicates with the built in caches in +the Lucee Engine + +*/ +component serializable="false" implements="wirebox.system.cache.ICacheProvider"{ + + /** + * Constructor + */ + LuceeProvider function init() output=false{ + // prepare instance data + instance = { + // provider name + name = "", + // provider enable flag + enabled = false, + // reporting enabled flag + reportingEnabled = false, + // configuration structure + configuration = {}, + // cacheFactory composition + cacheFactory = "", + // event manager composition + eventManager = "", + // storage composition, even if it does not exist, depends on cache + store = "", + // the cache identifier for this provider + cacheID = createObject('java','java.lang.System').identityHashCode(this), + // Element Cleaner Helper + elementCleaner = CreateObject("component","wirebox.system.cache.util.ElementCleaner").init(this), + // Utilities + utility = createObject("component","wirebox.system.core.util.Util"), + // our UUID creation helper + uuidHelper = createobject("java", "java.util.UUID") + }; + + // Provider Property Defaults + instance.DEFAULTS = { + cacheName = "object" + }; + + return this; + } + + /** + * get the cache name + */ + any function getName() output=false{ + return instance.name; + } + + /** + * set the cache name + */ + void function setName(required name) output=false{ + instance.name = arguments.name; + } + + /** + * set the event manager + */ + void function setEventManager(required any EventManager) output=false{ + instance.eventManager = arguments.eventManager; + } + + /** + * get the event manager + */ + any function getEventManager() output=false{ + return instance.eventManager; + } + + /** + * get the cache configuration structure + */ + any function getConfiguration() output=false{ + return instance.configuration; + } + + /** + * set the cache configuration structure + */ + void function setConfiguration(required any configuration) output=false{ + instance.configuration = arguments.configuration; + } + + /** + * get the associated cache factory + */ + any function getCacheFactory() output=false{ + return instance.cacheFactory; + } + + /** + * Validate the incoming configuration and make necessary defaults + **/ + private void function validateConfiguration(){ + var cacheConfig = getConfiguration(); + var key = ""; + + // Validate configuration values, if they don't exist, then default them to DEFAULTS + for(key in instance.DEFAULTS){ + if( NOT structKeyExists(cacheConfig, key) OR NOT len(cacheConfig[key]) ){ + cacheConfig[key] = instance.DEFAULTS[key]; + } + } + } + + /** + * configure the cache for operation + */ + void function configure() output=false{ + var config = getConfiguration(); + var props = []; + + lock name="LuceeProvider.config.#instance.cacheID#" type="exclusive" throwontimeout="true" timeout="20"{ + + // Prepare the logger + instance.logger = getCacheFactory().getLogBox().getLogger( this ); + + if( instance.logger.canDebug() ) + instance.logger.debug( "Starting up LuceeProvider Cache: #getName()# with configuration: #config.toString()#" ); + + // Validate the configuration + validateConfiguration(); + + // enabled cache + instance.enabled = true; + instance.reportingEnabled = true; + + if( instance.logger.canDebug() ) + instance.logger.debug( "Cache #getName()# started up successfully" ); + } + + } + + /** + * shutdown the cache + */ + void function shutdown() output=false{ + //nothing to shutdown + if( instance.logger.canDebug() ) + instance.logger.debug( "LuceeProvider Cache: #getName()# has been shutdown." ); + } + + /* + * Indicates if cache is ready for operation + */ + any function isEnabled() output=false{ + return instance.enabled; + } + + /* + * Indicates if cache is ready for reporting + */ + any function isReportingEnabled() output=false{ + return instance.reportingEnabled; + } + + /* + * Get the cache statistics object as wirebox.system.cache.util.ICacheStats + * @doc_generic wirebox.system.cache.util.ICacheStats + */ + any function getStats() output=false{ + return createObject("component", "wirebox.system.cache.providers.lucee-lib.LuceeStats").init( this ); + } + + /** + * clear the cache stats: Not enabled in this provider + */ + void function clearStatistics() output=false{ + // not yet posible with lucee + } + + /** + * Returns the underlying cache engine: Not enabled in this provider + */ + any function getObjectStore() output=false{ + // not yet possible with lucee + //return cacheGetSession( getConfiguration().cacheName ); + } + + /** + * get the cache's metadata report + */ + any function getStoreMetadataReport() output=false{ + var md = {}; + var keys = getKeys(); + var item = ""; + + for(item in keys){ + md[item] = getCachedObjectMetadata(item); + } + + return md; + } + + /** + * Get a key lookup structure where cachebox can build the report on. Ex: [timeout=timeout,lastAccessTimeout=idleTimeout]. It is a way for the visualizer to construct the columns correctly on the reports + */ + any function getStoreMetadataKeyMap() output="false"{ + var keyMap = { + timeout = "timespan", hits = "hitcount", lastAccessTimeout = "idleTime", + created = "createdtime", LastAccessed = "lasthit" + }; + return keymap; + } + + /** + * get all the keys in this provider + */ + any function getKeys() output=false{ + try{ + if( isDefaultCache() ){ + return cacheGetAllIds(); + } + + return cacheGetAllIds( "", getConfiguration().cacheName ); + } + catch(Any e){ + instance.logger.error( "Error retrieving all keys from cache: #e.message# #e.detail#", e.stacktrace ); + return [ "Error retrieving keys from cache: #e.message#" ]; + } + } + + /** + * get an object's cached metadata + */ + any function getCachedObjectMetadata(required any objectKey) output=false{ + if( isDefaultCache() ){ + return cacheGetMetadata( arguments.objectKey ); + } + + return cacheGetMetadata( arguments.objectKey, getConfiguration().cacheName ); + } + + /** + * get an item from cache + */ + any function get(required any objectKey) output=false{ + + if( isDefaultCache() ){ + return cacheGet( arguments.objectKey ); + } + else{ + return cacheGet( arguments.objectKey, false, getConfiguration().cacheName ); + } + } + + /** + * get an item silently from cache, no stats advised: Stats not available on lucee + */ + any function getQuiet(required any objectKey) output=false{ + // not implemented by lucee yet + return get(arguments.objectKey); + } + + /** + * Not implemented by this cache + */ + any function isExpired(required any objectKey) output=false{ + return false; + } + + /** + * check if object in cache + */ + any function lookup(required any objectKey) output=false{ + if( isDefaultCache() ){ + return cachekeyexists(arguments.objectKey ); + } + return cachekeyexists(arguments.objectKey, getConfiguration().cacheName ); + } + + /** + * check if object in cache with no stats: Stats not available on lucee + */ + any function lookupQuiet(required any objectKey) output=false{ + // not possible yet on lucee + return lookup(arguments.objectKey); + } + + /** + * Tries to get an object from the cache, if not found, it calls the 'produce' closure to produce the data and cache it + */ + any function getOrSet( + required any objectKey, + required any produce, + any timeout="0", + any lastAccessTimeout="0", + any extra={} + ){ + + var refLocal = { + object = get( arguments.objectKey ) + }; + + // Verify if it exists? if so, return it. + if( structKeyExists( refLocal, "object" ) ){ return refLocal.object; } + + // else, produce it + lock name="CacheBoxProvider.GetOrSet.#instance.cacheID#.#arguments.objectKey#" type="exclusive" timeout="10" throwonTimeout="true"{ + // double lock + refLocal.object = get( arguments.objectKey ); + if( not structKeyExists( refLocal, "object" ) ){ + // produce it + refLocal.object = arguments.produce(); + // store it + set( objectKey=arguments.objectKey, + object=refLocal.object, + timeout=arguments.timeout, + lastAccessTimeout=arguments.lastAccessTimeout, + extra=arguments.extra ); + } + } + + return refLocal.object; + } + + /** + * set an object in cache + */ + any function set(required any objectKey, + required any object, + any timeout="0", + any lastAccessTimeout="0", + any extra) output=false{ + + setQuiet(argumentCollection=arguments); + + //ColdBox events + var iData = { + cache = this, + cacheObject = arguments.object, + cacheObjectKey = arguments.objectKey, + cacheObjectTimeout = arguments.timeout, + cacheObjectLastAccessTimeout = arguments.lastAccessTimeout + }; + getEventManager().processState("afterCacheElementInsert",iData); + + return true; + } + + /** + * set an object in cache with no advising to events + */ + any function setQuiet(required any objectKey, + required any object, + any timeout="0", + any lastAccessTimeout="0", + any extra) output=false{ + + // check if incoming timoeut is a timespan or minute to convert to timespan + if( !findnocase("timespan", arguments.timeout.getClass().getName() ) ){ + if( !isNumeric( arguments.timeout ) ){ arguments.timeout = 0; } + arguments.timeout = createTimeSpan(0,0,arguments.timeout,0); + } + if( !findnocase("timespan", arguments.lastAccessTimeout.getClass().getName() ) ){ + if( !isNumeric( arguments.lastAccessTimeout ) ){ arguments.lastAccessTimeout = 0; } + arguments.lastAccessTimeout = createTimeSpan(0,0,arguments.lastAccessTimeout,0); + } + // Cache it + if( isDefaultCache() ){ + cachePut(arguments.objectKey,arguments.object,arguments.timeout,arguments.lastAccessTimeout); + } + else{ + cachePut(arguments.objectKey,arguments.object,arguments.timeout,arguments.lastAccessTimeout, getConfiguration().cacheName); + } + + return true; + } + + /** + * get cache size + */ + any function getSize() output=false{ + if( isDefaultCache() ){ + return cacheCount(); + } + return cacheCount( getConfiguration().cacheName ); + } + + /** + * Not implemented by this cache + */ + void function reap() output=false{ + // Not implemented by this provider + } + + /** + * clear all elements from cache + */ + void function clearAll() output=false{ + var iData = { + cache = this + }; + + if( isDefaultCache() ){ + cacheClear(); + } + else{ + cacheClear("",getConfiguration().cacheName); + } + + // notify listeners + getEventManager().processState("afterCacheClearAll",iData); + } + + /** + * clear an element from cache + */ + any function clear(required any objectKey) output=false{ + + if( isDefaultCache() ){ + cacheRemove( arguments.objectKey ); + } + else{ + cacheRemove( arguments.objectKey ,false, getConfiguration().cacheName ); + } + + //ColdBox events + var iData = { + cache = this, + cacheObjectKey = arguments.objectKey + }; + getEventManager().processState("afterCacheElementRemoved",iData); + + return true; + } + + /** + * clear with no advising to events + */ + any function clearQuiet(required any objectKey) output=false{ + // normal clear, not implemented by lucee + clear(arguments.objectKey); + return true; + } + + /** + * Clear by key snippet + */ + void function clearByKeySnippet(required keySnippet, regex=false, async=false) output=false{ + var threadName = "clearByKeySnippet_#replace(instance.uuidHelper.randomUUID(),"-","","all")#"; + + // Async? IF so, do checks + if( arguments.async AND NOT instance.utility.inThread() ){ + thread name="#threadName#" keySnippet="#arguments.keySnippet#" regex="#arguments.regex#"{ + instance.elementCleaner.clearByKeySnippet( attribues.keySnippet, attribues.regex ); + } + } + else{ + instance.elementCleaner.clearByKeySnippet( arguments.keySnippet, arguments.regex ); + } + } + + /** + * not implemented by cache + */ + void function expireAll() output=false{ + // Not implemented by this cache + } + + /** + * not implemented by cache + */ + void function expireObject(required any objectKey) output=false{ + //not implemented + } + + /** + * Checks if the default cache is in use + */ + private any function isDefaultCache(){ + return ( getConfiguration().cacheName EQ instance.DEFAULTS.cacheName ); + } + + /** + * set the associated cache factory + */ + void function setCacheFactory(required any cacheFactory) output=false{ + instance.cacheFactory = arguments.cacheFactory; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/providers/MockProvider.cfc b/src/cfml/system/_wirebox/system/cache/providers/MockProvider.cfc new file mode 100644 index 000000000..a6da8918c --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/providers/MockProvider.cfc @@ -0,0 +1,238 @@ + + + + + + + super.init(); + + instance.cache = {}; + + return this; + + + + + + + instance.cache = {}; + instance.enabled = true; + instance.reportingEnabled = true; + + + + + + + + + + + + + + + + + + + + + var keyMap = { + timeout = "timeout", hits = "hits", lastAccessTimeout = "lastAccessTimeout", + created = "created", LastAccessed = "LastAccessed", isExpire="isExpired" + }; + return keymap; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/providers/cf-lib/CFStats.cfc b/src/cfml/system/_wirebox/system/cache/providers/cf-lib/CFStats.cfc new file mode 100644 index 000000000..d607f38cc --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/providers/cf-lib/CFStats.cfc @@ -0,0 +1,68 @@ +/** +******************************************************************************** +Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +www.ortussolutions.com +******************************************************************************** +Author: Luis Majano +Description: + +A coldfusion statistics object that communicates with the CF ehCache stats + +*/ +component implements="wirebox.system.cache.util.ICacheStats" accessors="true"{ + + property name="cacheStats" serializable="false"; + + CFStats function init( stats ) output=false{ + setCacheStats( arguments.stats ); + return this; + } + + any function getCachePerformanceRatio() output=false{ + var hits = getHits(); + var requests = hits + getMisses(); + + if ( requests eq 0){ + return 0; + } + + return (hits/requests) * 100; + } + + any function getObjectCount() output=false{ + return getCacheStats().getObjectCount(); + } + + void function clearStatistics() output=false{ + getCacheStats().clearStatistics(); + } + + any function getGarbageCollections() output=false{ + return 0; + } + + any function getEvictionCount() output=false{ + return getCacheStats().getEvictionCount(); + } + + any function getHits() output=false{ + return getCacheStats().getCacheHits(); + } + + any function getMisses() output=false{ + return getCacheStats().getCacheMisses(); + } + + any function getLastReapDatetime() output=false{ + return ""; + } + + /******************************************************* + ehCache specific functions + ********************************************************/ + any function getAverageGetTime(){ + return getCacheStats().getAverageGetTime(); + } + +} + diff --git a/src/cfml/system/_wirebox/system/cache/providers/lucee-lib/LuceeStats.cfc b/src/cfml/system/_wirebox/system/cache/providers/lucee-lib/LuceeStats.cfc new file mode 100644 index 000000000..8aeb5088b --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/providers/lucee-lib/LuceeStats.cfc @@ -0,0 +1,68 @@ +/** +******************************************************************************** +Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +www.ortussolutions.com +******************************************************************************** +Author: Luis Majano +Description: + +A coldfusion statistics object that communicates with the lucee cache stats + +*/ +component implements="wirebox.system.cache.util.ICacheStats" accessors="true"{ + + property name="cacheProvider" serializable="false"; + + LuceeStats function init( cacheProvider ) output=false{ + setCacheProvider( arguments.cacheProvider ); + return this; + } + + any function getCachePerformanceRatio() output=false{ + var hits = getHits(); + var requests = hits + getMisses(); + + if ( requests eq 0){ + return 0; + } + + return (hits/requests) * 100; + } + + any function getObjectCount() output=false{ + return cacheCount( getCacheProvider().getConfiguration().cacheName ); + } + + void function clearStatistics() output=false{ + // not yet implemented by lucee + } + + any function getGarbageCollections() output=false{ + return 0; + } + + any function getEvictionCount() output=false{ + return 0; + } + + any function getHits() output=false{ + var props = cacheGetProperties( getCacheProvider().getConfiguration().cacheName ); + if( arrayLen( props ) and structKeyExists( props[ 1 ], "hit_count" ) ){ + return props[ 1 ].hit_count; + } + return 0; + } + + any function getMisses() output=false{ + var props = cacheGetProperties( getCacheProvider().getConfiguration().cacheName ); + if( arrayLen( props ) and structKeyExists( props[ 1 ], "miss_count" ) ){ + return props[ 1 ].miss_count; + } + return 0; + } + + any function getLastReapDatetime() output=false{ + return ""; + } +} + diff --git a/src/cfml/system/wirebox/system/cache/providers/lucee-lib/RailoStats.cfc b/src/cfml/system/_wirebox/system/cache/providers/lucee-lib/RailoStats.cfc similarity index 100% rename from src/cfml/system/wirebox/system/cache/providers/lucee-lib/RailoStats.cfc rename to src/cfml/system/_wirebox/system/cache/providers/lucee-lib/RailoStats.cfc diff --git a/src/cfml/system/_wirebox/system/cache/readme.md b/src/cfml/system/_wirebox/system/cache/readme.md new file mode 100644 index 000000000..f535e12ba --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/readme.md @@ -0,0 +1,100 @@ +``` + _____ _ ____ + / ____| | | | _ \ + | | __ _ ___| |__ ___| |_) | _____ __ + | | / _` |/ __| '_ \ / _ \ _ < / _ \ \/ / + | |___| (_| | (__| | | | __/ |_) | (_) > < + \_____\__,_|\___|_| |_|\___|____/ \___/_/\_\ + +``` + +Copyright Since 2005 ColdBox Platform by Luis Majano and Ortus Solutions, Corp +www.coldbox.org | www.ortussolutions.com + +---- + +Because of God's grace, this project exists. If you don't like this, then don't read it, its not for you. + +>"Therefore being justified by faith, we have peace with God through our Lord Jesus Christ: +By whom also we have access by faith into this grace wherein we stand, and rejoice in hope of the glory of God. +And not only so, but we glory in tribulations also: knowing that tribulation worketh patience; +And patience, experience; and experience, hope: +And hope maketh not ashamed; because the love of God is shed abroad in our hearts by the +Holy Ghost which is given unto us. ." Romans 5:5 + +---- + +# Welcome to CacheBox +CacheBox is an enterprise caching engine, aggregator and API for ColdFusion (CFML) applications. + +## License +Apache License, Version 2.0. + +>The ColdBox Websites, logo and content have a separate license and they are a separate entity. + +## Versioning +CacheBox is maintained under the Semantic Versioning guidelines as much as possible. + +Releases will be numbered with the following format: + +``` +.. +``` + +And constructed with the following guidelines: + +* Breaking backward compatibility bumps the major (and resets the minor and patch) +* New additions without breaking backward compatibility bumps the minor (and resets the patch) +* Bug fixes and misc changes bumps the patch + +## Important Links + +Source Code +- https://github.com/coldbox/coldbox-platform + +Continuous Integration +- http://jenkins.staging.ortussolutions.com/job/OS-ColdBoxPlatform%20BE/ + +Bug Tracking/Agile Boards +- https://ortussolutions.atlassian.net/browse/CACHEBOX + +Documentation +- http://cachebox.ortusbooks.com +- http://wiki.coldbox.org/wiki/CacheBox.cfm + +Blog +- http://blog.coldbox.org + +Official Site +- http://www.coldbox.org + +## System Requirements +- Lucee 4.5+ +- ColdFusion 11+ + +## Quick Installation +Please go to our [documentation](http://cachebox.ortusbooks.com) for expanded instructions. + +**CommandBox (Recommended)** + +We recommend you use [CommandBox](http://www.ortussolutions.com/products/commandbox), our CFML CLI and package manager, to install CacheBox. + +**Stable Release** + +`box install cachebox` + +**Bleeding Edge Release** + +`box install cachebox-be` + +**Simple Install** + +Unzip the download into a folder called `cachebox` in your webroot or place outside of the webroot and create a per-application mapping `/cachebox` that points to it. + +**Bleeding Edge Downloads** +You can always leverage our bleeding edge artifacts server to download CacheBox: http://integration.staging.ortussolutions.com/artifacts/ortussolutions/cachebox/ + +--- + +###THE DAILY BREAD + > "I am the way, and the truth, and the life; no one comes to the Father, but by me (JESUS)" Jn 14:1-12 \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/report/ReportHandler.cfc b/src/cfml/system/_wirebox/system/cache/report/ReportHandler.cfc new file mode 100644 index 000000000..c2c1dbbda --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/report/ReportHandler.cfc @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + variables.cacheBox = arguments.cacheBox; + variables.baseURL = arguments.baseURL; + variables.runtime = createObject("java", "java.lang.Runtime"); + variables.skin = arguments.skin; + variables.skinPath = "/wirebox/system/cache/report/skins/#arguments.skin#"; + // Store tag attributes so they are available on skin templates. + variables.attributes = arguments.attributes; + // Caller references + variables.caller = arguments.caller; + + return this; + + + + + + + + + + + + // Commands + switch(arguments.command){ + // Cache Commands + case "expirecache" : { cacheBox.getCache(arguments.cacheName).expireAll(); break; } + case "clearcache" : { cacheBox.getCache(arguments.cacheName).clearAll(); break; } + case "reapcache" : { cacheBox.getCache(arguments.cacheName).reap(); break;} + case "delcacheentry" : { cacheBox.getCache(arguments.cacheName).clear( arguments.cacheEntry );break;} + case "expirecacheentry" : { cacheBox.getCache(arguments.cacheName).expireObject( arguments.cacheEntry );break;} + case "clearallevents" : { cacheBox.getCache(arguments.cacheName).clearAllEvents();break;} + case "clearallviews" : { cacheBox.getCache(arguments.cacheName).clearAllViews();break;} + case "cacheBoxReapAll" : { cacheBox.reapAll();break;} + case "cacheBoxExpireAll" : { cacheBox.expireAll();break;} + case "gc" : { runtime.getRuntime().gc(); break;} + + default: return false; + } + + return true; + + + + + + + var content = ""; + var cacheNames = cacheBox.getCacheNames(); + var URLBase = baseURL; + + + + + + + + + + + + + var content = ""; + + // Cache info + var cacheProvider = cacheBox.getCache( arguments.cacheName ); + var cacheConfig = ""; + var cacheStats = ""; + var cacheSize = cacheProvider.getSize(); + var isCacheBox = true; + + // JVM Data + var JVMRuntime = runtime.getRuntime(); + var JVMFreeMemory = JVMRuntime.freeMemory()/1024; + var JVMTotalMemory = JVMRuntime.totalMemory()/1024; + var JVMMaxMemory = JVMRuntime.maxMemory()/1024; + + // URL Base + var URLBase = baseURL; + + // Prepare cache report for cachebox + cacheConfig = cacheProvider.getConfiguration(); + cacheStats = cacheProvider.getStats(); + + + + + + + + + + + + + var thisKey = ""; + var x = ""; + var content = ""; + var cacheProvider = cacheBox.getCache( arguments.cacheName ); + var cacheKeys = ""; + var cacheKeysLen = 0; + var cacheMetadata = ""; + var cacheMDKeyLookup = structnew(); + + // URL Base + var URLBase = baseURL; + + // Cache Data + cacheMetadata = cacheProvider.getStoreMetadataReport(); + cacheMDKeyLookup = cacheProvider.getStoreMetadataKeyMap(); + cacheKeys = cacheProvider.getKeys(); + cacheKeysLen = arrayLen( cacheKeys ); + + // Sort Keys + arraySort( cacheKeys ,"textnocase" ); + + + + + + + + + + + + + + + + + + + + + + + + + + + + #cachekey# = #cacheValue# + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/report/monitor.cfm b/src/cfml/system/_wirebox/system/cache/report/monitor.cfm new file mode 100644 index 000000000..672bfe78d --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/report/monitor.cfm @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #report# + + + + + + + + + + + + +#report# + diff --git a/src/cfml/system/_wirebox/system/cache/report/skins/default/CacheCharting.cfm b/src/cfml/system/_wirebox/system/cache/report/skins/default/CacheCharting.cfm new file mode 100644 index 000000000..fe9f9a309 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/report/skins/default/CacheCharting.cfm @@ -0,0 +1,46 @@ +
+ + + + + +
+ + + + + + + +
+ + + + +
+ +
+ + + + + + + + + + + + + + + + + +
+
\ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/report/skins/default/CacheContentReport.cfm b/src/cfml/system/_wirebox/system/cache/report/skins/default/CacheContentReport.cfm new file mode 100644 index 000000000..64fa37d57 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/report/skins/default/CacheContentReport.cfm @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + class="even" id="cbox_cache_tr_#urlEncodedFormat(thisKey)#"> + + + + + + + + + + + + + + + + + + + + +
ObjectHitsTimeoutIdle TimeoutCreatedLast AccessedStatusCMDS
+ + #thisKey# + + #cacheMetadata[thisKey][ cacheMDKeyLookup.hits ]##cacheMetadata[thisKey][ cacheMDKeyLookup.timeout ]##cacheMetadata[thisKey][ cacheMDKeyLookup.lastAccessTimeout ]# + + #dateformat( cacheMetadata[thisKey][ cacheMDKeyLookup.Created ], "mmm-dd" )#
+ #timeformat( cacheMetadata[thisKey][ cacheMDKeyLookup.created ], "hh:mm:ss tt" )# +
+
+ + #dateformat(cacheMetadata[thisKey][ cacheMDKeyLookup.LastAccessed ],"mmm-dd")#
+ #timeformat(cacheMetadata[thisKey][ cacheMDKeyLookup.LastAccessed ],"hh:mm:ss tt")# +
+
+ + Expired + + Alive + + + + +
+
\ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/report/skins/default/CachePanel.cfm b/src/cfml/system/_wirebox/system/cache/report/skins/default/CachePanel.cfm new file mode 100644 index 000000000..bdf282f6a --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/report/skins/default/CachePanel.cfm @@ -0,0 +1,99 @@ + + + + + + +
 CacheBox Monitor
+ +
+ + +
+ + + + + + Refresh Monitor: + + + + + + + + + + + + + + + Loading... +
+ + +
+ CacheBox ID +
+
+ #cacheBox.getFactoryID()# +
+
+ Configured Caches +
+
+ #arrayToList(cacheBox.getCacheNames())# +
+
+ Scope Registration +
+
+ #cacheBox.getScopeRegistration().toString()# +
+
+ + +

Performance Report For + + Cache + + + + Loading... +

+ + +
#renderCacheReport()#
+ +
+
\ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/report/skins/default/CacheReport.cfm b/src/cfml/system/_wirebox/system/cache/report/skins/default/CacheReport.cfm new file mode 100644 index 000000000..511f9d2c2 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/report/skins/default/CacheReport.cfm @@ -0,0 +1,128 @@ + + + + + +
+ Cache Name +
+
+ #cacheProvider.getName()# [class=#getMetadata(cacheProvider).name#] +
+ + +
+ Performance +
+
+ Hit Ratio: #NumberFormat(cacheStats.getCachePerformanceRatio(),"999.99")#% ==> + Hits: #cacheStats.getHits()# | + Misses: #cacheStats.getMisses()# | + Evictions: #cacheStats.getEvictionCount()# | + Garbage Collections: #cacheStats.getGarbageCollections()# | + Object Count: #cacheSize# +
+ + +
+ JVM Memory Stats +
+
+ #NumberFormat((JVMFreeMemory/JVMMaxMemory)*100,"99.99")# % Free | + Max: #NumberFormat(JVMMaxMemory)# KB + Total: #NumberFormat(JVMTotalMemory)# KB | + Free: #NumberFormat(JVMFreeMemory)# KB +
+ + + +
+ Last Reap +
+
+ #DateFormat(cacheStats.getlastReapDatetime(),"MMM-DD-YYYY")# + #TimeFormat(cacheStats.getlastReapDatetime(),"hh:mm:ss tt")# +
+
+ + + + + +

Cache Configuration + +

+
+ + + + + + + + + + + class="even"> + + + + + + + +
+ + + +

Cache Content Report

+ + + + + + + + + + + + + + + + + + + + Please Wait, Processing... + +
+ #renderCacheContentReport(arguments.cacheName)# +
+
+
\ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/report/skins/default/cachebox.css b/src/cfml/system/_wirebox/system/cache/report/skins/default/cachebox.css new file mode 100644 index 000000000..b2629059a --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/report/skins/default/cachebox.css @@ -0,0 +1,190 @@ +.cachebox_debugPanel{ + font-family: Verdana,Arial,sans-serif; + font-size: 11px; + font-weight: normal; + color: #000000; + text-align: left; + width:98%; + margin: auto; +} +.cachebox_titles{ + font-size: 10px; + font-weight: bold; + color: white; + border: 1px solid #2694E8; + background: #3BAAE3 url(/wirebox/system/cache/report/skins/default/images/bg-glass.png) repeat-x scroll 50% 50%; + padding:5px 5px 5px 5px; + cursor: pointer; + margin-bottom: 2px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + -khtml-border-radius: 5px; +} +.cachebox_titles:hover{ + background: #D7EBF9 url(/wirebox/system/cache/report/skins/default/images/bg-glass2.png) repeat-x scroll 50% 50%; + border:1px solid #D3D3D3; + cursor: pointer; + color: black; +} +.cachebox_debugContentView{ + color: black; + background-color: #f6f6f6; + border:1px solid #D3D3D3; + padding: 5px 5px 5px 5px; + display:block; + margin:auto; + font-size:11px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + -khtml-border-radius: 5px; +} +.cachebox_tracerMessage{ + padding:5px 5px 5px 5px; + border:1px solid #3366CC; + background-color: #ffffff; + color: black; +} +.cachebox_debugContent{ + display:none; +} + +.cachebox_debugTitleCell{ + font-weight:bold; + float:left; + width: 135px; + clear:left; + height: 20px; +} +.cachebox_debugContentCell{ + clear:right; + height: 20px; +} +.cachebox_renderTime{ + margin-top: 20px; + margin-bottom: 20px; + font-weight: bold; + font-style: italic; +} +.cachebox_redText{ + font-weight:bold; + color:#CC0000; +} +.cachebox_orangeText{ + font-weight: bold; + color: #ff4432; +} +.cachebox_greenText{ + color: #336600; + font-weight:bold; +} +.cachebox_blueText{ + font-weight:bold; + color: #0022AA; +} +.cachebox_blackText{ + font-weight:bold; + color: #000000; +} +.cachebox_purpleText{ + font-weight:bold; + color: #67306A; +} +.cachebox_errorText{ + font-size: 11px; + color: #666666; + font-weight:bold; +} +.cachebox_debugTables{ + font-size: 11px; + border:1px outset #93C2FF; + background-color: #eeeeee; + width: 99%; + margin-left:auto; + margin-right:auto; +} +.cachebox_debugTables th{ + font-size: 11px; + background-color: #CFE9FF; + font-weight:bold; + padding: 5px 5px 5px 5px; +} +.cachebox_debugTables tr{ + background-color: white; +} +.cachebox_debugTables tr:hover{ + background-color: #FEFFAF; +} +.cachebox_debugTables tr.even{ + background-color: #EFF6FF; +} +.cachebox_debugTables tr.even:hover{ + background-color: #FEFFAF; +} +.cachebox_debugTables td{ + padding: 5px 5px 5px 5px; + font-size: 11px; +} +.cachebox_debugTables textarea{ + border: 1px solid #eeeeee; + background-color: #fffff0; + font-size: 11px; +} +.cachebox_debugTables tr.showRC{ + display: table-row; +} +.cachebox_debugTables tr.hideRC{ + display: none; +} +.cachebox_cacheContentReport{ + overflow: auto; + height: 375px; + border: 2px inset #cccccc; + background-color: white; + text-align: center; +} +.cachebox_errorTables{ + font-size: 11px; + font-family: verdana; + border:1px outset #000000; + background-color: #FFFFFF; + width: 99%; +} +.cachebox_errorTables th{ + background-color: #969491; + color:#000000; + font-weight: bold; + padding:5px 5px 5px 5px; +} +.cachebox_errorTables td{ + font-size: 11px; + background-color: #ffffff; + border: 1px dotted #cccccc; + padding: 5px 5px 5px 5px; +} +.cachebox_errorTables td.cachebox_errorTablesTitles{ + font-size: 11px; + font-weight: bold; + background-color: #EDEDEA; + padding: 5px 5px 5px 5px; +} +.cachebox_overflowScroll{ + width: 600px; + overflow:scroll; + height: 300px; +} +.cachebox_errorDiv{ + font-size:11px; + font-family: verdana; + margin: 5px; + padding: 5px; +} +.cachebox_errorDiv h3{ + margin-top: 3px; + margin-bottom: 3px; + color: #993333; +} +.cachebox_errorNotice{ + padding: 5px; + background-color: #FFF6CC; + border: 1px solid #999999; +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/report/skins/default/cachebox.js b/src/cfml/system/_wirebox/system/cache/report/skins/default/cachebox.js new file mode 100644 index 000000000..b9687a175 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/report/skins/default/cachebox.js @@ -0,0 +1,134 @@ +function cachebox_toggle(divid){ + if ( document.getElementById(divid).className == "cachebox_debugContent"){ + document.getElementById(divid).className = "cachebox_debugContentView"; + } + else{ + document.getElementById(divid).className = "cachebox_debugContent"; + } +} +function cachebox_openwindow(mypage,myname,w,h,features) { + if(screen.width){ + var winl = (screen.width-w)/2; + var wint = (screen.height-h)/2; + } + else{ + winl = 0;wint =0; + } + if (winl < 0) winl = 0; + if (wint < 0) wint = 0; + + var settings = 'height=' + h + ','; + settings += 'width=' + w + ','; + settings += 'top=' + wint + ','; + settings += 'left=' + winl + ','; + settings += features; + win = window.open(mypage,myname,settings); + win.window.focus(); +} +function cachebox_getBase(baseURL){ + var idx = baseURL.search(/\?/); + if( idx > 0 ) + return baseURL + "&"; + return baseURL + "?"; +} +function cachebox_pollmonitor(panel, frequency, urlBase,newWindow){ + var newLocation = cachebox_getBase(urlBase) + 'debugpanel=' + panel + '&frequency=' + frequency + '&cbox_cacheMonitor=true'; + if (newWindow == 'undefined') { + window.location = newLocation; + } + else{ + cachebox_openwindow(newLocation,'cachemonitor',850,750,'status=1,toolbar=0,location=0,resizable=1,scrollbars=1'); + } +} +function cachebox_cboxCommand( commandURL, verb ){ + if( verb == null ){ + verb = "GET"; + } + var request = new XMLHttpRequest(); + request.open( verb, commandURL, false); + request.send(); + return request.responseText; +} +function cachebox_cacheExpireItem(URLBase, cacheKey, cacheName){ + // Button + var btn = document.getElementById('cboxbutton_expireentry_'+cacheKey); + btn.disabled = true; + btn.value = "Wait"; + // Execute Command + cachebox_cboxCommand( cachebox_getBase(URLBase) + "cbox_command=expirecacheentry&cbox_cacheentry=" + cacheKey + "&cbox_cacheName="+cacheName); + // ReFill the content report + cachebox_cacheContentReport(URLBase,cacheName); +} +function cachebox_cacheClearItem(URLBase, cacheKey, cacheName){ + // Button + var btn = document.getElementById('cboxbutton_removeentry_'+cacheKey); + btn.disabled = true; + btn.value = "Wait"; + // Execute Command + cachebox_cboxCommand( cachebox_getBase(URLBase) + "cbox_command=delcacheentry&cbox_cacheentry=" + cacheKey + "&cbox_cacheName="+cacheName); + // Remove Entry + var element = document.getElementById('cbox_cache_tr_'+cacheKey); + element.parentNode.removeChild(element); +} +//Execute a command from the cacheBox Toolbar +function cachebox_cacheBoxCommand(URLBase, command, btnID, showAlert){ + if( showAlert == null){ showAlert = true; } + var btn = document.getElementById(btnID) + btn.disabled = true + cachebox_cboxCommand( cachebox_getBase(URLBase) + "cbox_command=" + command) + btn.disabled = false + if( showAlert ){ alert("CacheBox Command Completed!"); } +} +// Execute a garbage collection +function cachebox_cacheGC(URLBase,cacheName,btnID){ + // Run GC + cachebox_cacheBoxCommand(URLBase,"gc",btnID,false) + // Re-Render Cache Report + cachebox_cacheReport(URLBase,cacheName) +} +// Render a cache report dynamically based on a cache name +function cachebox_cacheReport(URLBase,cacheName){ + // loader change + var loader = document.getElementById('cachebox_cachebox_selector_loading'); + loader.style.display='inline'; + var reportDiv = document.getElementById('cachebox_cacheReport'); + reportDiv.innerHTML = ""; + // get report + var reportHTML = cachebox_cboxCommand(cachebox_getBase(URLBase)+"debugPanel=cacheReport&cbox_cacheName="+cacheName); + reportDiv.innerHTML = reportHTML; + // turn off loader + loader.style.display='none'; +} +// Re-Fill Content Report +function cachebox_cacheContentReport(URLBase,cacheName){ + var reportDiv = document.getElementById('cachebox_cacheContentReport') + reportDiv.innerHTML = "" + var reportHTML = cachebox_cboxCommand(cachebox_getBase(URLBase)+"debugPanel=cacheContentReport&cbox_cacheName="+cacheName); + reportDiv.innerHTML = reportHTML; +} +//Execute a CacheContent Command +function cachebox_cacheContentCommand(URLBase, command, cacheName, loaderDiv){ + var loader = document.getElementById('cachebox_cacheContentReport_loader'); + loader.style.display='inline'; + // Execute Command + cachebox_cboxCommand( cachebox_getBase(URLBase) + "cbox_command=" + command + "&cbox_cacheName="+cacheName); + // ReFill the content report + cachebox_cacheContentReport(URLBase,cacheName); + // turn off loader + loader.style.display='none'; +} +function cachebox_toggleDiv(targetDiv, displayStyle){ + // toggle a div with styles, man I miss jquery + if( displayStyle == null){ displayStyle = "block"; } + var target = document.getElementById(targetDiv); + if( target.style.display == displayStyle ){ + target.style.display = "none"; + } + else{ + target.style.display = displayStyle; + } +} +// Timed Refresh +function cachebox_timedRefresh(timeoutPeriod) { + setTimeout("location.reload(true);",timeoutPeriod); +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/report/skins/default/images/bg-glass.png b/src/cfml/system/_wirebox/system/cache/report/skins/default/images/bg-glass.png new file mode 100644 index 0000000000000000000000000000000000000000..baabca6baaff94ade4ecd5ddad28e35f52ea3af7 GIT binary patch literal 131 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAK^akKnouq`W*`978O6-=1H{+u$H^^ufM+ zvdx>+CU0QuV-!dbkTBu=d3O3OBmLW&4uK;3Q}6PdBzSM&05zNqru zes!`qYix+t4(>@$KQ4S5FeM|FA))8zv=y~toE^Ja);%%|y_LML;(w3fvtta7-CP_U Ta$7zCO=9qL^>bP0l+XkKumdMp literal 0 HcmV?d00001 diff --git a/src/cfml/system/_wirebox/system/cache/store/BlackholeStore.cfc b/src/cfml/system/_wirebox/system/cache/store/BlackholeStore.cfc new file mode 100644 index 000000000..5da10fecf --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/store/BlackholeStore.cfc @@ -0,0 +1,119 @@ + + + + + + + + + + // Store Fields + var fields = "hits,timeout,lastAccessTimeout,created,LastAccessed,isExpired,isSimple"; + var config = arguments.cacheProvider.getConfiguration(); + + // Prepare instance + instance = { + cacheProvider = arguments.cacheProvider, + storeID = 'blackhole' + }; + + return this; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/store/ConcurrentSoftReferenceStore.cfc b/src/cfml/system/_wirebox/system/cache/store/ConcurrentSoftReferenceStore.cfc new file mode 100644 index 000000000..8a46799e6 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/store/ConcurrentSoftReferenceStore.cfc @@ -0,0 +1,251 @@ + + + + + + + + + + // Super size me + super.init( arguments.cacheProvider ); + + // Override Fields + instance.indexer.setFields( instance.indexer.getFields() & ",isSoftReference"); + + // Prepare soft reference lookup maps + instance.softRefKeyMap = CreateObject("java","java.util.concurrent.ConcurrentHashMap").init(); + instance.referenceQueue = CreateObject("java","java.lang.ref.ReferenceQueue").init(); + + return this; + + + + + + + + + super.clearAll(); + instance.softRefKeyMap.clear(); + + + + + + + + + + + + // Init Ref Key Vars + refLocal.collected = instance.referenceQueue.poll(); + + // Let's reap the garbage collected soft references + while( structKeyExists(reflocal, "collected") ){ + + // Clean if it still exists + if( softRefLookup( reflocal.collected ) ){ + + // expire it + expireObject( getSoftRefKey(refLocal.collected) ); + + // GC Collection Hit + instance.cacheProvider.getStats().gcHit(); + } + + // Poll Again + reflocal.collected = instance.referenceQueue.poll(); + } + + + + + + + + + + + + + + // check existence via super, if not found, check as it might be a soft reference + if( NOT super.lookup( arguments.objectKey ) ){ return false; } + // get quiet to test it as it might be a soft reference + refLocal.target = getQuiet( arguments.objectKey ); + // is it found? + if( NOT structKeyExists(refLocal,"target") ){ return false; } + + // if we get here, it is found + return true; + + + + + + + + + var refLocal = {}; + + // Get via concurrent store + refLocal.target = super.get( arguments.objectKey ); + if( !isNull( refLocal.target ) ){ + + // Validate if SR or normal object + if( isInstanceOf(refLocal.target, "java.lang.ref.SoftReference") ){ + return refLocal.target.get(); + } + + return refLocal.target; + } + + + + + + + + var refLocal = {}; + + // Get via concurrent store + refLocal.target = super.getQuiet( arguments.objectKey ); + + if( !isNull( refLocal.target ) ){ + + // Validate if SR or normal object + if( isInstanceOf(refLocal.target, "java.lang.ref.SoftReference") ){ + return refLocal.target.get(); + } + + return refLocal.target; + } + + + + + + + + + + + + + + + + + + + + + // Check for eternal object + if( isSR ){ + // Cache as soft reference not an eternal object + target = createSoftReference(arguments.objectKey,arguments.object); + } + else{ + target = arguments.object; + } + + // Store it + super.set(objectKey=arguments.objectKey, + object=target, + timeout=arguments.timeout, + lastAccessTimeout=arguments.lastAccessTimeout, + extras=arguments.extras); + + // Set extra md in indexer + instance.indexer.setObjectMetadataProperty(arguments.objectKey,"isSoftReference", isSR ); + + + + + + + + + + + + + + // Check if it exists + if( NOT structKeyExists(instance.pool, arguments.objectKey) ){ + return false; + } + + // Is this a soft reference? + softRef = instance.pool[arguments.objectKey]; + + // Removal of Soft Ref Lookup + if( instance.indexer.getObjectMetadataProperty(arguments.objectKey,"isSoftReference") ){ + structDelete(getSoftRefKeyMap(),softRef); + } + + return super.clear( arguments.objectKey ); + + + + + + + + + + + + + + + + + + + + + + + + + var keyMap = getSoftRefKeyMap(); + + if( structKeyExists(keyMap,arguments.softRef) ){ + return keyMap[arguments.softRef]; + } + + + + + + + + + + + + // Create Soft Reference Wrapper and register with Queue + var softRef = CreateObject("java","java.lang.ref.SoftReference").init(arguments.target,getReferenceQueue()); + var refKeyMap = getSoftRefKeyMap(); + + // Create Reverse Mapping + refKeyMap[ softRef ] = arguments.objectKey; + + return softRef; + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/store/ConcurrentStore.cfc b/src/cfml/system/_wirebox/system/cache/store/ConcurrentStore.cfc new file mode 100644 index 000000000..f9d54e6e0 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/store/ConcurrentStore.cfc @@ -0,0 +1,213 @@ + + + + + + + + + + // Indexing Fields + var fields = "hits,timeout,lastAccessTimeout,created,LastAccessed,isExpired"; + + // Prepare instance + instance = { + cacheProvider = arguments.cacheProvider, + storeID = createObject('java','java.lang.System').identityHashCode(this), + pool = createObject("java","java.util.concurrent.ConcurrentHashMap").init(), + indexer = createObject("component","wirebox.system.cache.store.indexers.MetadataIndexer").init(fields) + }; + + return this; + + + + + + + + + + + + + + + + + + + + + + instance.pool.clear(); + instance.indexer.clearAll(); + + + + + + + + + + + + + + + + + + + + + + + + + // Check if object in pool and object not dead + if( structKeyExists( instance.pool , arguments.objectKey) + AND instance.indexer.objectExists( arguments.objectKey ) + AND NOT instance.indexer.getObjectMetadataProperty(arguments.objectKey,"isExpired") ){ + return true; + } + + return false; + + + + + + + + + + + + + // retrieve from map + refLocal.results = instance.pool.get( arguments.objectKey ); + if( !isNull( refLocal.results ) ){ + + // Record Metadata Access + instance.indexer.setObjectMetadataProperty(arguments.objectKey,"hits", instance.indexer.getObjectMetadataProperty(arguments.objectKey,"hits")+1); + instance.indexer.setObjectMetadataProperty(arguments.objectKey,"LastAccessed", now()); + + // return object + return refLocal.results; + } + + + + + + + + + + + + // retrieve from map + refLocal.results = instance.pool.get( arguments.objectKey ); + if( structKeyExists(refLocal,"results") ){ + return refLocal.results; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + // Set new Object into cache pool + instance.pool[arguments.objectKey] = arguments.object; + + // Create object's metdata + metaData = { + hits = 1, + timeout = arguments.timeout, + lastAccessTimeout = arguments.LastAccessTimeout, + created = now(), + LastAccessed = now(), + isExpired = false + }; + + // Save the object's metadata + instance.indexer.setObjectMetadata(arguments.objectKey, metaData); + + + + + + + + + + + + + + // Check if it exists + if( NOT structKeyExists(instance.pool, arguments.objectKey) ){ + return false; + } + + // Remove it + structDelete(instance.pool, arguments.objectKey); + instance.indexer.clear( arguments.objectKey ); + + // Removed + return true; + + + + + + + + + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/store/DiskStore.cfc b/src/cfml/system/_wirebox/system/cache/store/DiskStore.cfc new file mode 100644 index 000000000..2aeb142b9 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/store/DiskStore.cfc @@ -0,0 +1,263 @@ + + + + + + + + + + // Store Fields + var fields = "hits,timeout,lastAccessTimeout,created,LastAccessed,isExpired,isSimple"; + var config = arguments.cacheProvider.getConfiguration(); + + // Prepare instance + instance = { + cacheProvider = arguments.cacheProvider, + storeID = createObject('java','java.lang.System').identityHashCode(this), + indexer = createObject("component","wirebox.system.cache.store.indexers.MetadataIndexer").init(fields), + converter = createObject("component","wirebox.system.core.conversion.ObjectMarshaller").init() + }; + + // Get extra configuration details from cacheProvider's configuration for this diskstore + // Auto Expand + if( NOT structKeyExists(config, "autoExpandPath") ){ + config.autoExpandPath = true; + } + + // Check directory path + if( NOT structKeyExists(config,"directoryPath") ){ + throw(message="The 'directoryPath' configuration property was not found in the cache configuration", + detail="Please check the cache configuration and add the 'directoryPath' property. Current Configuration: #config.toString()#", + type="DiskStore.InvalidConfigurationException"); + } + + //AutoExpand + if( config.autoExpandPath ){ + instance.directoryPath = expandPath( config.directoryPath ); + } + else{ + instance.directoryPath = config.directoryPath; + } + + //Check if directory exists else create it + if( NOT directoryExists( instance.directoryPath ) ){ + directoryCreate( instance.directoryPath ); + } + + return this; + + + + + + + + + + + + + + + + + + + + + + directoryDelete( instance.directoryPath, true ); + instance.indexer.clearAll(); + directoryCreate( instance.directoryPath ); + + + + + + + + + + + + + + + + + + + + // check if object is missing and in indexer + if( NOT fileExists( getCacheFilePath( arguments.objectKey ) ) AND instance.indexer.objectExists( arguments.objectKey ) ){ + instance.indexer.clear( arguments.objectKey ); + return false; + } + + // Check if object on disk, on indexer and NOT expired + if( fileExists( getCacheFilePath( arguments.objectKey ) ) + AND instance.indexer.objectExists( arguments.objectKey ) + AND NOT instance.indexer.getObjectMetadataProperty(arguments.objectKey,"isExpired") ){ + return true; + } + + return false; + + + + + + + + + + + + if( lookup(arguments.objectKey) ){ + // Record Metadata Access + instance.indexer.setObjectMetadataProperty(arguments.objectKey,"hits", instance.indexer.getObjectMetadataProperty(arguments.objectKey,"hits")+1); + instance.indexer.setObjectMetadataProperty(arguments.objectKey,"LastAccessed", now()); + + return getQuiet( arguments.objectKey ); + } + + + + + + + + + + + + + + if( lookup( arguments.objectKey ) ){ + + // if simple value, just return it + if( instance.indexer.getObjectMetadataProperty( arguments.objectKey, "isSimple" ) ){ + return trim( fileRead( thisFilePath ) ); + } + + //else we deserialize + return instance.converter.deserializeObject( filePath = thisFilePath ); + + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + // If simple value just write it out to disk + if( isSimpleValue(arguments.object) ){ + fileWrite( thisFilePath, trim( arguments.object ) ); + } + else{ + // serialize it + instance.converter.serializeObject(arguments.object, thisFilePath); + metaData.isSimple = false; + } + // Save the object's metadata + instance.indexer.setObjectMetadata(arguments.objectKey, metaData); + + + + + + + + + + + + + // check it + if( NOT fileExists( thisFilePath ) ){ + return false; + } + // Remove it + fileDelete( thisFilePath ); + instance.indexer.clear( arguments.objectKey ); + + return true; + + + + + + + + + + + + + + + + return instance.directoryPath & "/" & hash(arguments.objectKey) & ".cachebox"; + + + + + + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/store/IObjectStore.cfc b/src/cfml/system/_wirebox/system/cache/store/IObjectStore.cfc new file mode 100644 index 000000000..9df88bf3f --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/store/IObjectStore.cfc @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/store/JDBCStore.cfc b/src/cfml/system/_wirebox/system/cache/store/JDBCStore.cfc new file mode 100644 index 000000000..3b6093ab7 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/store/JDBCStore.cfc @@ -0,0 +1,456 @@ + + + + + + + + + + // Store Fields + var fields = "objectKey,hits,timeout,lastAccessTimeout,created,lastAccessed,isExpired,isSimple"; + var config = arguments.cacheProvider.getConfiguration(); + + // Prepare instance + instance = { + storeID = createObject('java','java.lang.System').identityHashCode( this ), + cacheProvider = arguments.cacheProvider, + converter = createObject("component","wirebox.system.core.conversion.ObjectMarshaller").init() + }; + + // Get Extra config data + instance.dsn = config.dsn; + instance.table = config.table; + + // Check credentials + if( NOT structKeyExists(config, "dsnUsername") ){ + config.dsnUsername = ""; + } + if( NOT structKeyExists(config, "dsnPassword") ){ + config.dsnPassword = ""; + } + instance.dsnUsername = config.dsnUsername; + instance.dsnPassword = config.dsnPassword; + + // Check autoCreate + if( NOT structKeyExists(config, "tableAutoCreate") ){ + config.tableAutoCreate = true; + } + instance.tableAutoCreate = config.tableAutoCreate; + + // ensure the table + if( config.tableAutoCreate ){ + ensureTable(); + } + + // Indexer + instance.indexer = createObject("component","wirebox.system.cache.store.indexers.JDBCMetadataIndexer").init(fields, config, this); + + return this; + + + + + + + + + + + + + + + + + + + + + + + + TRUNCATE TABLE #instance.table# + + + + + + + + + + + + + + + SELECT objectKey + FROM #instance.table# + ORDER BY objectKey ASC + + + + + + + + + + + + + + + SELECT id, isExpired + FROM #instance.table# + WHERE id = + + + + + + + + + + + var q = lookupQuery( arguments.objectKey ); + + // Check if object in pool + if( q.recordCount AND NOT q.isExpired){ + return true; + } + + return false; + + + + + + + + + + + + + + + + SELECT * + FROM #instance.table# + WHERE id = + + + + + + UPDATE #instance.table# + SET lastAccessed = , + hits = hits + 1 + WHERE id = + + + + + + + // Just return if records found, else null + if( q.recordCount ){ + + // if simple value, just return it + if( q.isSimple ){ + return q.objectValue; + } + + //else we return deserialized + return instance.converter.deserializeObject(binaryObject=q.objectValue); + } + + + + + + + + + + + + + SELECT * + FROM #instance.table# + WHERE id = + + + + // Just return if records found, else null + if( q.recordCount ){ + + // if simple value, just return it + if( q.isSimple ){ + return q.objectValue; + } + + //else we return deserialized + return instance.converter.deserializeObject(binaryObject=q.objectValue); + } + + + + + + + + + + + + UPDATE #instance.table# + SET isExpired = + WHERE id = + + + + + + + + + + + + + + SELECT isExpired + FROM #instance.table# + WHERE id = + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + INSERT INTO #instance.table# (id,objectKey,objectValue,hits,timeout,lastAccessTimeout,created,lastAccessed,isExpired,isSimple) + VALUES ( + , + , + , + , + , + , + , + , + , + + ) + + + + + + + + UPDATE #instance.table# + SET objectKey = , + objectValue = , + hits = , + timeout = , + lastAccessTimeout = , + created = , + lastAccessed = , + isExpired = , + isSimple = + WHERE id = + + + + + + + + + + + + + + + + + + + + DELETE + FROM #instance.table# + WHERE id = + + + + + + + + + + + + SELECT count(id) as totalCount + FROM #instance.table# + + + + + + + + + + return hash( arguments.objectKey ); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CREATE TABLE #instance.table# ( + id VARCHAR(100) NOT NULL, + objectKey VARCHAR(255) NOT NULL, + objectValue #create.valueType# NOT NULL, + hits #create.intType# NOT NULL DEFAULT '1', + timeout #create.intType# NOT NULL, + lastAccessTimeout integer NOT NULL, + created #create.timeType# NOT NULL, + lastAccessed #create.timeType# NOT NULL, + isExpired #create.booleanType# NOT NULL DEFAULT '1', + isSimple #create.booleanType# NOT NULL DEFAULT '0', + PRIMARY KEY (id) + ) #create.afterCreate# + + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/store/indexers/JDBCMetadataIndexer.cfc b/src/cfml/system/_wirebox/system/cache/store/indexers/JDBCMetadataIndexer.cfc new file mode 100644 index 000000000..70efa8e1e --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/store/indexers/JDBCMetadataIndexer.cfc @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + super.init(arguments.fields); + + // store db sql compatibility type + instance.sqlType = "MySQL"; + if( findNoCase("Microsoft SQL", DBData.database_productName) ){ + instance.sqlType = "MSSQL"; + } + + // store jdbc configuration + instance.config = arguments.config; + + // store storage reference + instance.store = arguments.store; + + return this; + + + + + + + + + + + + + + + // Normalize fields + if( isArray(arguments.fields) ){ + arguments.fields = arrayToList( arguments.fields ); + } + + // Store fields + instance.fields = arguments.fields; + + + + + + + + + + + + + SELECT id + FROM #instance.config.table# + WHERE id = + + + + + + + + + + + + + + SELECT TOP 100 #instance.fields# + FROM #instance.config.table# + ORDER BY objectKey + LIMIT 100 + + + + + + + + + + + + + + + + + + + + + SELECT #instance.fields# + FROM #instance.config.table# + WHERE id = + + + + + + + + + + + + + + + + + + + + + SELECT #arguments.property# as prop + FROM #instance.config.table# + WHERE id = + + + + + + + + + + + + + + + + + + + + + SELECT id, objectKey + FROM #instance.config.table# + ORDER BY #arguments.property# #arguments.sortOrder# + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/store/indexers/MetadataIndexer.cfc b/src/cfml/system/_wirebox/system/cache/store/indexers/MetadataIndexer.cfc new file mode 100644 index 000000000..5e935cb8e --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/store/indexers/MetadataIndexer.cfc @@ -0,0 +1,146 @@ + + + + + + + + + instance = { + // Create metadata pool + poolMetadata = CreateObject("java","java.util.concurrent.ConcurrentHashMap").init(), + // Index ID + indexID = createObject('java','java.lang.System').identityHashCode(this) + }; + + // Store Fields + setFields( arguments.fields ); + + return this; + + + + + + + + + + + + + + + // Normalize fields + if( isArray(arguments.fields) ){ + arguments.fields = arrayToList( arguments.fields ); + } + + // Store fields + instance.fields = arguments.fields; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + validateField( arguments.property ); + + return structSort( instance.poolMetadata, arguments.sortType, arguments.sortOrder, arguments.property ); + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/store/sql/JDBCStore-MSSQL.sql b/src/cfml/system/_wirebox/system/cache/store/sql/JDBCStore-MSSQL.sql new file mode 100644 index 000000000..06eb31d34 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/store/sql/JDBCStore-MSSQL.sql @@ -0,0 +1,81 @@ +CREATE TABLE [dbo].[cacheBox] ( + [id] varchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, + [objectKey] varchar(255) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, + [objectValue] ntext COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, + [hits] int CONSTRAINT [DF_cacheBox_hits] DEFAULT 1 NOT NULL, + [timeout] int NOT NULL, + [lastAccessTimeout] int NOT NULL, + [created] datetime NOT NULL, + [lastAccessed] datetime NOT NULL, + [isExpired] tinyint CONSTRAINT [DF_cacheBox_isExpired] DEFAULT 0 NOT NULL, + [isSimple] tinyint CONSTRAINT [DF_cacheBox_isSimple] DEFAULT 1 NOT NULL, + CONSTRAINT [PK_cacheBox] PRIMARY KEY CLUSTERED ([id]) +) +ON [PRIMARY] +TEXTIMAGE_ON [PRIMARY] +GO + +CREATE NONCLUSTERED INDEX [created] ON [dbo].[cacheBox] + ([created]) +WITH ( + PAD_INDEX = OFF, + DROP_EXISTING = OFF, + STATISTICS_NORECOMPUTE = OFF, + SORT_IN_TEMPDB = OFF, + ONLINE = OFF, + ALLOW_ROW_LOCKS = ON, + ALLOW_PAGE_LOCKS = ON) +ON [PRIMARY] +GO + +CREATE NONCLUSTERED INDEX [hits] ON [dbo].[cacheBox] + ([hits]) +WITH ( + PAD_INDEX = OFF, + DROP_EXISTING = OFF, + STATISTICS_NORECOMPUTE = OFF, + SORT_IN_TEMPDB = OFF, + ONLINE = OFF, + ALLOW_ROW_LOCKS = ON, + ALLOW_PAGE_LOCKS = ON) +ON [PRIMARY] +GO + +CREATE NONCLUSTERED INDEX [isExpired] ON [dbo].[cacheBox] + ([isExpired]) +WITH ( + PAD_INDEX = OFF, + DROP_EXISTING = OFF, + STATISTICS_NORECOMPUTE = OFF, + SORT_IN_TEMPDB = OFF, + ONLINE = OFF, + ALLOW_ROW_LOCKS = ON, + ALLOW_PAGE_LOCKS = ON) +ON [PRIMARY] +GO + +CREATE NONCLUSTERED INDEX [lastAccessed] ON [dbo].[cacheBox] + ([lastAccessed]) +WITH ( + PAD_INDEX = OFF, + DROP_EXISTING = OFF, + STATISTICS_NORECOMPUTE = OFF, + SORT_IN_TEMPDB = OFF, + ONLINE = OFF, + ALLOW_ROW_LOCKS = ON, + ALLOW_PAGE_LOCKS = ON) +ON [PRIMARY] +GO + +CREATE NONCLUSTERED INDEX [timeout] ON [dbo].[cacheBox] + ([timeout]) +WITH ( + PAD_INDEX = OFF, + DROP_EXISTING = OFF, + STATISTICS_NORECOMPUTE = OFF, + SORT_IN_TEMPDB = OFF, + ONLINE = OFF, + ALLOW_ROW_LOCKS = ON, + ALLOW_PAGE_LOCKS = ON) +ON [PRIMARY] +GO \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/store/sql/JDBCStore-MySQL.sql b/src/cfml/system/_wirebox/system/cache/store/sql/JDBCStore-MySQL.sql new file mode 100644 index 000000000..2c705b0aa --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/store/sql/JDBCStore-MySQL.sql @@ -0,0 +1,18 @@ +CREATE TABLE `cacheBox` ( + `id` varchar(100) NOT NULL, + `objectKey` varchar(255) NOT NULL, + `objectValue` longtext NOT NULL, + `hits` int(11) NOT NULL DEFAULT '1', + `timeout` int(11) NOT NULL, + `lastAccessTimeout` int(11) NOT NULL, + `created` datetime NOT NULL, + `lastAccessed` datetime NOT NULL, + `isExpired` tinyint(4) NOT NULL DEFAULT '0', + `isSimple` tinyint(4) NOT NULL DEFAULT '1', + PRIMARY KEY (`id`), + KEY `hits` (`hits`), + KEY `created` (`created`), + KEY `lastAccessed` (`lastAccessed`), + KEY `timeout` (`timeout`), + KEY `isExpired` (`isExpired`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/store/sql/JDBCStore-Postgres.sql b/src/cfml/system/_wirebox/system/cache/store/sql/JDBCStore-Postgres.sql new file mode 100644 index 000000000..17202efb7 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/store/sql/JDBCStore-Postgres.sql @@ -0,0 +1,33 @@ +CREATE TABLE cachebox ( + id VARCHAR(100) NOT NULL, + objectKey VARCHAR(255) NOT NULL, + objectValue text NOT NULL, + hits integer NOT NULL DEFAULT '1', + timeout integer NOT NULL, + lastAccessTimeout integer NOT NULL, + created timestamp NOT NULL, + lastAccessed timestamp NOT NULL, + isExpired boolean NOT NULL DEFAULT true, + isSimple boolean NOT NULL DEFAULT false, + PRIMARY KEY (id) +) +CREATE INDEX created + ON cachebox + USING btree + (created); +CREATE INDEX hits + ON cachebox + USING btree + (hits); +CREATE INDEX "isExpired" + ON cachebox + USING btree + (isexpired); +CREATE INDEX "lastAccessed" + ON cachebox + USING btree + (lastaccessed); +CREATE INDEX timeout + ON cachebox + USING btree + (timeout); \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/util/CacheStats.cfc b/src/cfml/system/_wirebox/system/cache/util/CacheStats.cfc new file mode 100644 index 000000000..a15db9a41 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/util/CacheStats.cfc @@ -0,0 +1,131 @@ + + + + + + + + + instance = { + cacheProvider = arguments.cacheProvider + }; + + // Init reap to right now + setLastReapDateTime(now()); + + // Clear the stats to start fresh. + clearStatistics(); + + return this; + + + + + + + + + + + + + + var requests = instance.hits + instance.misses; + + if ( requests eq 0){ + return 0; + } + + return (instance.hits/requests) * 100; + + + + + + + + + + + + instance.hits = 0; + instance.misses = 0; + instance.evictionCount = 0; + instance.garbageCollections = 0; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + instance.evictionCount++; + + + + + + + instance.garbageCollections++; + + + + + + + instance.hits++; + + + + + + + instance.misses++; + + + + + + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/util/ElementCleaner.cfc b/src/cfml/system/_wirebox/system/cache/util/ElementCleaner.cfc new file mode 100644 index 000000000..9e02138e7 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/util/ElementCleaner.cfc @@ -0,0 +1,188 @@ + + + + + + + + + variables.cacheProvider = arguments.cacheProvider; + return this; + + + + + + + + + + + + + + + + + + var cacheKeys = getAssociatedCache().getKeys(); + var cacheKeysLength = arrayLen(cacheKeys); + var x = 1; + var tester = 0; + var thisKey = ""; + + // sort array + arraySort(cacheKeys, "textnocase"); + + for(x=1; x lte cacheKeysLength; x++){ + // Get List Value + thisKey = cacheKeys[x]; + + // Using Regex + if( arguments.regex ){ + tester = refindnocase( arguments.keySnippet, thisKey ); + } + else{ + tester = findnocase( arguments.keySnippet, thisKey ); + } + + // Test Evaluation + if ( tester ){ + getAssociatedCache().clear( thisKey ); + } + } + + + + + + + + + //.*- = the cache suffix and appendages for regex to match + var cacheKey = getAssociatedCache().getEventCacheKeyPrefix() & replace( arguments.eventsnippet, ".", "\.", "all" ) & ".*-.*"; + + //Check if we are purging with query string + if( len( arguments.queryString ) neq 0 ){ + cacheKey &= "-" & getAssociatedCache().getEventURLFacade().buildHash( arguments.queryString ); + } + + // Clear All Events by Criteria + clearByKeySnippet( keySnippet=cacheKey, regex=true ); + + + + + + + + + var regexCacheKey = ""; + var x = 1; + var cacheKey = ""; + var keyPrefix = getAssociatedCache().getEventCacheKeyPrefix(); + var eventURLFacade = getAssociatedCache().getEventURLFacade(); + + // normalize snippets + if( isArray(arguments.eventSnippets) ){ + arguments.eventsnippets = arrayToList( arguments.eventsnippets ); + } + + // Loop on the incoming snippets + for(x=1;x lte listLen(arguments.eventsnippets);x=x+1){ + + //.*- = the cache suffix and appendages for regex to match + cacheKey = keyPrefix & replace(listGetAt(arguments.eventsnippets,x),".","\.","all") & "-.*"; + + //Check if we are purging with query string + if( len(arguments.queryString) neq 0 ){ + cacheKey = cacheKey & "-" & eventURLFacade.buildHash(listGetAt(arguments.queryString,x)); + } + regexCacheKey = regexCacheKey & cacheKey; + + //check that we aren't at the end of the list, and the | char to the regex as the OR statement + if (x NEQ listLen(arguments.eventsnippets)) { + regexCacheKey = regexCacheKey & "|"; + } + } + + // Clear All Events by Criteria + clearByKeySnippet(keySnippet=regexCacheKey,regex=true); + + + + + + + var cacheKey = getAssociatedCache().getEventCacheKeyPrefix(); + + // Clear All Events + clearByKeySnippet(keySnippet=cacheKey,regex=false); + + + + + + + + var cacheKey = getAssociatedCache().getViewCacheKeyPrefix() & arguments.viewSnippet; + + // Clear All View snippets + clearByKeySnippet(keySnippet=cacheKey,regex=false); + + + + + + + + var regexCacheKey = ""; + var x = 1; + var cacheKey = ""; + var keyPrefix = getAssociatedCache().getViewCacheKeyPrefix(); + + // normalize snippets + if( isArray(arguments.viewSnippets) ){ + arguments.viewSnippets = arrayToList( arguments.viewSnippets ); + } + + // Loop on the incoming snippets + for(x=1;x lte listLen(arguments.viewSnippets);x=x+1){ + + //.*- = the cache suffix and appendages for regex to match + cacheKey = keyPrefix & replace(listGetAt(arguments.viewSnippets,x),".","\.","all") & "-.*"; + + //Check if we are purging with query string + regexCacheKey = regexCacheKey & cacheKey; + + //check that we aren't at the end of the list, and the | char to the regex as the OR statement + if (x NEQ listLen(arguments.viewSnippets)) { + regexCacheKey = regexCacheKey & "|"; + } + } + + // Clear All Events by Criteria + clearByKeySnippet(keySnippet=regexCacheKey,regex=true); + + + + + + + var cacheKey = getAssociatedCache().getViewCacheKeyPrefix(); + + // Clear All the views + clearByKeySnippet(keySnippet=cacheKey,regex=false); + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/util/EventURLFacade.cfc b/src/cfml/system/_wirebox/system/cache/util/EventURLFacade.cfc new file mode 100644 index 000000000..e0e3ced17 --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/util/EventURLFacade.cfc @@ -0,0 +1,107 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * This cfc acts as an URL/FORM facade for event caching. The associated cache + * will have to implement the IColdboxApplicationCache in order to retrieve the right + * prefix keys. + */ +component accessors="true"{ + + // Connected Provider + property name="cacheProvider"; + + /** + * Constructor + * @cacheProvider Provider to connect to + */ + function init( required cacheProvider ){ + variables.cacheProvider = arguments.cacheProvider; + return this; + } + + /** + * Build a unique hash from an incoming request context + * + * @event A request context object + */ + string function getUniqueHash( required event ){ + var incomingHash = hash( + arguments.event.getCollection().filter( function( key, value ){ + // Remove event, not needed for hashing purposes + return ( key != "event" ); + } ).toString() + ); + var targetMixer = { + // Get the original incoming context hash + "incomingHash" = incomingHash, + // Multi-Host support + "cgihost" = cgi.http_host + }; + + // Incorporate Routed Structs + structAppend( targetMixer, arguments.event.getRoutedStruct(), true ); + + // Return unique identifier + return hash( targetMixer.toString() ); + } + + /** + * Build a unique hash according to an incoming query string, mostly used when calling the clear functions of + * cache providers + * + * @args A querystring based argument collection + */ + function buildHash( required string args ){ + var virtualRC = {}; + arguments.args + .listToArray( "&" ) + .each( function( item ){ + virtualRC[ item.getToken( 1, "=" ).trim() ] = urlDecode( item.getToken( 2, "=" ).trim() ); + } ); + + //writeDump( var = "==> Hash Args Struct: #virtualRC.toString()#", output="console" ); + var myStruct = { + // Get the original incoming context hash according to incoming arguments + "incomingHash" = hash( virtualRC.toString() ), + // Multi-Host support + "cgihost" = cgi.http_host + }; + + // return hash from cache key struct + return hash( myStruct.toString() ); + } + + /** + * Build an event key according to passed in params + * + * @keySuffix The key suffix used in the cache key + * @targetEvent The targeted ColdBox event executed + * @targetContext The targeted request context object + */ + string function buildEventKey( required keySuffix, required targetEvent, required targetContext ){ + return buildBasicCacheKey( argumentCollection=arguments ) & getUniqueHash( arguments.targetContext ); + } + + /** + * Build an event key according to passed in params + * + * @keySuffix The key suffix used in the cache key + * @targetEvent The targeted ColdBox event executed + * @targetArgs A query string based argument collection like a query string + */ + string function buildEventKeyNoContext( required keySuffix, required targetEvent, required targetArgs ){ + return buildBasicCacheKey( argumentCollection=arguments ) & buildHash( arguments.targetArgs ); + } + + /** + * Builds a basic cache key without the hash component + * @keySuffix The key suffix used + * @targetEvent The targetged ColdBox event string + */ + string function buildBasicCacheKey( required keySuffix, required targetEvent ){ + return variables.cacheProvider.getEventCacheKeyPrefix() & arguments.targetEvent & "-" & arguments.keySuffix & "-"; + } + + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/cache/util/ICacheStats.cfc b/src/cfml/system/_wirebox/system/cache/util/ICacheStats.cfc new file mode 100644 index 000000000..8f269448c --- /dev/null +++ b/src/cfml/system/_wirebox/system/cache/util/ICacheStats.cfc @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/core/collections/ScopeStorage.cfc b/src/cfml/system/_wirebox/system/core/collections/ScopeStorage.cfc new file mode 100644 index 000000000..bb3e6a920 --- /dev/null +++ b/src/cfml/system/_wirebox/system/core/collections/ScopeStorage.cfc @@ -0,0 +1,140 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* A utility Facade to help in storing data in multiple CF Storages +*/ +component{ + + // Static list of valid scopes + variables.SCOPES = "application|client|cookie|session|server|request"; + + /** + * Constructor + */ + function init(){ + return this; + } + + /** + * Store a value in a scope + * @key The key + * @value The value + * @scope The ColdFusion Scope + */ + function put( required key, required value, required scope ){ + var scopePointer = getScope( arguments.scope ); + scopePointer[ arguments.key ] = arguments.value; + return this; + } + + /** + * Delete a value in a scope + * @key The key + * @scope The ColdFusion Scope + */ + boolean function delete( required key, required scope ){ + return structDelete( getScope( arguments.scope ), arguments.key, true ); + } + + /** + * Get a value in a scope + * @key The key + * @scope The CF Scope + * @defaultValue The default value + */ + function get( required key, required scope, defaultValue ){ + // Do stupid ACF Hack due to choking on `default` argument. + if( structKeyExists( arguments, "default" ) ){ + arguments.defaultValue = arguments.default; + } + + if( exists( arguments.key, arguments.scope ) ){ + return structfind( getscope( arguments.scope ), arguments.key ); + } else if ( structKeyExists( arguments, "defaultValue" ) ){ + return arguments.defaultValue; + } + + throw( + type = "ScopeStorage.KeyNotFound", + message = "The key #arguments.key# does not exist in the #arguments.scope# scope." + ); + } + + /** + * Check if a key exists + * @key The key + * @scope The CF Scope + */ + boolean function exists( required key, required scope ){ + return structKeyExists( getScope( arguments.scope ), arguments.key ); + } + + /** + * Get a scope reference + * @scope The CF Scope + */ + any function getScope( required scope ){ + scopeCheck( arguments.scope ); + + switch( arguments.scope ){ + case "session" : { + return ( isDefined( "session" ) ? session : {} ); + } + case "application" : return application; + case "server" : return server; + case "client" : return client; + case "cookie" : return cookie; + case "request" : return request; + } + } + + /** + * Shortcut to get Session + */ + any function getSession(){ + return getScope( "session" ); + } + + /** + * Shortcut to get Application + */ + any function getApplication(){ + return getScope( "application" ); + } + + /** + * Shortcut to get Client + */ + any function getClient(){ + return getScope( "client" ); + } + + /** + * Shortcut to get Server + */ + any function getServer(){ + return getScope( "server" ); + } + + /** + * Shortcut to get cookie + */ + any function getCookie(){ + return getScope( "cookie" ); + } + + /** + * Check if a scope is valid, else throws exception + * @scope The CF Scope + */ + any function scopeCheck( required scope ){ + if( NOT reFindNoCase( "^(#variables.SCOPES#)$", arguments.scope ) ){ + throw( + type = "ScopeStorage.InvalidScope", + message = "Invalid CF Scope, valid scopes are #variables.SCOPES#" + ); + } + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/core/conversion/DataMarshaller.cfc b/src/cfml/system/_wirebox/system/core/conversion/DataMarshaller.cfc new file mode 100644 index 000000000..35375c2dc --- /dev/null +++ b/src/cfml/system/_wirebox/system/core/conversion/DataMarshaller.cfc @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + // marshall to JSON + results = serializeJSON( arguments.data, arguments.jsonQueryFormat ); + // wrap results in callback function for JSONP + if( len( arguments.jsonCallback ) > 0 ){ + results = "#arguments.jsonCallback#(#results#)"; + } + + + + + + + + + + + + args.data = arguments.data; + args.encoding = arguments.encoding; + args.useCDATA = arguments.xmlUseCDATA; + args.delimiter = arguments.xmlListDelimiter; + args.rootName = arguments.xmlRootName; + if( len( trim( arguments.xmlColumnList ) ) ){ + args.columnlist = arguments.xmlColumnList; + } + // Marshal to xml + results = xmlConverter.toXML( argumentCollection=args ); + + + + + + + + + + + + + #arguments.data# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/core/conversion/ObjectMarshaller.cfc b/src/cfml/system/_wirebox/system/core/conversion/ObjectMarshaller.cfc new file mode 100644 index 000000000..6154f78d6 --- /dev/null +++ b/src/cfml/system/_wirebox/system/core/conversion/ObjectMarshaller.cfc @@ -0,0 +1,96 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* Allows you to serialize/deserialize objects +*/ +component accessors="true"{ + + /** + * Constructor + */ + function init(){ + return this; + } + + /** + * Serialize an object and optionally save it into a file. + * @target The complex object, such as a query or CFC, that will be serialized. + * @filePath The path of the file in which to save the serialized data. + */ + function serializeObject( required any target, string filePath ){ + var binaryData = serializeWithObjectSave( arguments.target ); + + // Save to File? + if( structKeyExists( arguments,"filePath" ) ){ + fileWrite( arguments.filePath, binaryData ); + } + + return binaryData; + } + + + /** + * Deserialize an object using a binary object or a filepath + * @target The binary object to inflate + * @filePath The location of the file that has the binary object to inflate + */ + function deserializeObject( any binaryObject, string filePath ){ + // Read From File? + if( structKeyExists( arguments,"filePath" ) ){ + arguments.binaryObject = fileRead( arguments.filePath ); + } + + return deserializeWithObjectLoad(arguments.binaryObject); + } + + /** + * Serialize via objectSave() + * @target The complex object, such as a query or CFC, that will be serialized. + */ + function serializeWithObjectSave( any target ){ + return toBase64( objectSave( arguments.target ) ); + } + + /** + * Deserialize via ObjectLoad + * @binaryObject The binary object to inflate + */ + function deserializeWithObjectLoad( any binaryObject ){ + // check if string + if( not isBinary( arguments.binaryObject ) ){ arguments.binaryObject = toBinary( arguments.binaryObject ); } + + return objectLoad( arguments.binaryObject ); + } + + /** + * Serialize via generic Java + * @target The binary object to inflate + */ + function serializeGeneric( any target ){ + var byteArrayOutput = createObject( "java", "java.io.ByteArrayOutputStream").init(); + var objectOutput = createObject( "java", "java.io.ObjectOutputStream").init( byteArrayOutput ); + + // Serialize the incoming object. + objectOutput.writeObject( arguments.target ); + objectOutput.close(); + + return toBase64( byteArrayOutput.toByteArray() ); + } + + /** + * Serialize via generic Java + * @target The binary object to inflate + */ + function deserializeGeneric( any binaryObject ){ + var byteArrayInput = createObject( "java", "java.io.ByteArrayInputStream").init( toBinary( arguments.binaryObject ) ); + var ObjectInput = createObject( "java", "java.io.ObjectInputStream").init( byteArrayInput ); + var obj = ""; + + obj = objectInput.readObject(); + objectInput.close(); + + return obj; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/core/conversion/XMLConverter.cfc b/src/cfml/system/_wirebox/system/core/conversion/XMLConverter.cfc new file mode 100644 index 000000000..0b64e6493 --- /dev/null +++ b/src/cfml/system/_wirebox/system/core/conversion/XMLConverter.cfc @@ -0,0 +1,356 @@ + + + + + + + + return this; + + + + + + + + + + + + + + + var buffer = createObject("java","java.lang.StringBuilder").init(''); + + // Header + if( arguments.addHeader ){ + buffer.append(''); + } + + // Object Check + if( isObject(arguments.data) ){ + buffer.append( objectToXML(argumentCollection=arguments) ); + } + // Struct Check? + else if( isStruct(arguments.data) ){ + buffer.append( structToXML(argumentCollection=arguments) ); + } + // Query Check? + else if( isQuery(arguments.data) ){ + buffer.append( queryToXML(argumentCollection=arguments) ); + } + // Array Check? + else if( isArray(arguments.data) ){ + buffer.append( arrayToXML(argumentCollection=arguments) ); + } + // Simple Value Check, treated as a simple array list? + else if( isSimpleValue(arguments.data) ){ + arguments.data = listToArray(arguments.data,arguments.delimiter); + buffer.append( arrayToXML(argumentCollection=arguments) ); + } + + return buffer.toString(); + + + + + + + + + + var buffer = createObject('java','java.lang.StringBuilder').init(''); + var target = arguments.data; + var x = 1; + var dataLen = arrayLen(target); + var thisValue = ""; + var rootElement = "array"; + var itemElement = "item"; + + // Root Name + if( len(arguments.rootName) ){ rootElement = arguments.rootName; } + + //Create Root + buffer.append("<#rootElement#>"); + + + + + + + + + + + + + + + + + + + #thisValue#")> + + + ")> + + + + + + + + + + + + + + + + + + + + + + + + + ')> + + + ')> + + + + + + + + + + + + + "> + + #value#")> + + + + + + ")> + + + + ")> + + + + + + + + + var target = arguments.data; + var buffer = createObject("java","java.lang.StringBuilder").init(''); + var key = 0; + var thisValue = ""; + var args = structnew(); + var rootElement = "struct"; + var objectType = ""; + + // Root Element + if( len(arguments.rootName) ){ rootElement = arguments.rootName; } + + // Declare Root + if( isObject(arguments.data) ){ + rootElement = "object"; + buffer.append('<#rootElement# type="#getMetadata(arguments.data).name#">'); + } + else{ + buffer.append("<#rootElement#>"); + } + + // Content + for(key in target){ + // Null Checks + if( !structKeyExists( target, key ) || isNull( target[key] ) ){ + target[key] = 'NULL'; + } + // Translate Value + if( NOT isSimpleValue(target[key]) ){ + thisValue = translateValue(arguments,target[key]); + } + else{ + thisValue = safeText(target[key],arguments.useCDATA); + } + buffer.append("<#lcase(key)#>#thisValue#"); + } + + // End Root + buffer.append(""); + + return buffer.toString(); + + + + + + + + + var target = isNull( arguments.data ) ? "NULL" : arguments.data; + var buffer = createObject("java","java.lang.StringBuilder").init(''); + var md = getMetadata(target); + var rootElement = lcase( safeText( listLast( md.name, "." ) ) ); + var thisName = ""; + var thisValue = ""; + var x = 0; + var newValue = ""; + + // Root Element Override + if( len(arguments.rootName) ){ rootElement = arguments.rootName; } + + // Declare Root + buffer.append('<#rootElement# type="#md.name#">'); + + // if no properties to marshall, then return blank + if( structKeyExists(md,"properties") ){ + + // loop over properties + for(x=1; x lte ArrayLen(md.properties); x=x+1){ + // check the property name exists and if it has a marshal annotation of false + if( structKeyExists(md.properties[x],"name") + OR NOT structKeyExists(md.properties[x],"marhsal") + OR md.properties[x]["marshal"] EQ true + ){ + thisName = md.properties[x].name; + thisValue = invoke( target, "get#thisName()#" ); + + // Value Defined? + if( not isDefined("thisValue") ){ + thisValue = ""; + } + + // Translate Value + if( NOT isSimpleValue( thisValue ) ){ + thisValue = translateValue(arguments, thisValue); + } + else{ + thisValue = safeText(thisValue,arguments.useCDATA); + } + + buffer.append("<#lcase(thisName)#>#thisValue#"); + + }//end if property has a name, else skip + + }// end loop over properties + + }// end if no properties detected + + // End Root + buffer.append(""); + + return buffer.toString(); + + + + + + + + + + var newArgs = structnew(); + newArgs.data = arguments.targetValue; + newArgs.useCDATA = arguments.args.useCDATA; + newArgs.addHeader = false; + return toXML(argumentCollection=newArgs); + + + + + + + + + + + "> + + + + + + + + + + var string = arguments.value; + string = replaceNoCase(string,chr(8218),'&##8218;','all'); // � + string = replaceNoCase(string,chr(402),'&##402;','all'); // � + string = replaceNoCase(string,chr(8222),'&##8222;','all'); // � + string = replaceNoCase(string,chr(8230),'&##8230;','all'); // � + string = replaceNoCase(string,chr(8224),'&##8224;','all'); // � + string = replaceNoCase(string,chr(8225),'&##8225;','all'); // � + string = replaceNoCase(string,chr(710),'&##710;','all'); // � + string = replaceNoCase(string,chr(8240),'&##8240;','all'); // � + string = replaceNoCase(string,chr(352),'&##352;','all'); // � + string = replaceNoCase(string,chr(8249),'&##8249;','all'); // � + string = replaceNoCase(string,chr(338),'&##338;','all'); // � + string = replaceNoCase(string,chr(8216),'&##8216;','all'); // � + string = replaceNoCase(string,chr(8217),'&##8217;','all'); // � + string = replaceNoCase(string,chr(8220),'&##8220;','all'); // � + string = replaceNoCase(string,chr(8221),'&##8221;','all'); // � + string = replaceNoCase(string,chr(8226),'&##8226;','all'); // � + string = replaceNoCase(string,chr(8211),'&##8211;','all'); // � + string = replaceNoCase(string,chr(8212),'&##8212;','all'); // � + string = replaceNoCase(string,chr(732),'&##732;','all'); // � + string = replaceNoCase(string,chr(8482),'&##8482;','all'); // � + string = replaceNoCase(string,chr(353),'&##353;','all'); // � + string = replaceNoCase(string,chr(8250),'&##8250;','all'); // � + string = replaceNoCase(string,chr(339),'&##339;','all'); // � + string = replaceNoCase(string,chr(376),'&##376;','all'); // � + string = replaceNoCase(string,chr(376),'&##376;','all'); // � + string = replaceNoCase(string,chr(8364),'&##8364','all'); // � + return string; + + + + + + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/core/dynamic/BeanPopulator.cfc b/src/cfml/system/_wirebox/system/core/dynamic/BeanPopulator.cfc new file mode 100644 index 000000000..d35d7c9b8 --- /dev/null +++ b/src/cfml/system/_wirebox/system/core/dynamic/BeanPopulator.cfc @@ -0,0 +1,511 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * This is a bean populator that binds different types of data to a bean. + */ +component{ + + /** + * Constructor + */ + function init(){ + variables.mixerUtil = new wirebox.system.core.dynamic.MixerUtil(); + variables.util = new wirebox.system.core.util.Util(); + return this; + } + + /** + * Populate a named or instantiated instance from a Json string + * + * @target The target to populate + * @JSONString The JSON string to populate the object with. It has to be valid JSON and also a structure with name-key value pairs. + * @scope Use scope injection instead of setters population. Ex: scope=variables.instance. + * @trustedSetter If set to true, the setter method will be called even if it does not exist in the bean + * @include A list of keys to include in the population + * @exclude A list of keys to exclude in the population + * @ignoreEmpty Ignore empty values on populations, great for ORM population + * @nullEmptyInclude A list of keys to NULL when empty + * @nullEmptyExclude A list of keys to NOT NULL when empty + * @composeRelationships Automatically attempt to compose relationships from memento + * + * @return The target populated with the packet + */ + function populateFromJson( + required target, + required string JSONString, + string scope="", + boolean trustedSetter=false, + string include="", + string exclude="", + boolean ignoreEmpty=false, + string nullEmptyInclude="", + string nullEmptyExclude="", + boolean composeRelationships=false + ){ + // Inflate JSON + arguments.memento = deserializeJSON( arguments.JSONString ); + + // populate and return + return populateFromStruct( argumentCollection=arguments ); + } + + /** + * Populate a named or instantiated instance from an XML Packet + * + * @target The target to populate + * @xml The XML string or packet to populate the target with + * @root The XML root element to start from, else defaults to XMLRoot + * @scope Use scope injection instead of setters population. Ex: scope=variables.instance. + * @trustedSetter If set to true, the setter method will be called even if it does not exist in the bean + * @include A list of keys to include in the population + * @exclude A list of keys to exclude in the population + * @ignoreEmpty Ignore empty values on populations, great for ORM population + * @nullEmptyInclude A list of keys to NULL when empty + * @nullEmptyExclude A list of keys to NOT NULL when empty + * @composeRelationships Automatically attempt to compose relationships from memento + * + * @return The target populated with the packet + */ + function populateFromXML( + required target, + required xml, + string root="", + string scope="", + boolean trustedSetter=false, + string include="", + string exclude="", + boolean ignoreEmpty=false, + string nullEmptyInclude="", + string nullEmptyExclude="", + boolean composeRelationships=false + ){ + // determine XML object or string? + if( isSimpleValue( arguments.xml ) ){ + arguments.xml = xmlParse( arguments.xml ); + } + + // check root else default to XMLRoot + if( NOT len( arguments.root ) ){ + arguments.root = "XMLRoot"; + } + + // check children else don't do anything, we can't populate + if( NOT structKeyExists( arguments.xml[ arguments.root ], "XMLChildren" ) ){ + return; + } + + arguments.memento = {}; + // Have to do it this way as ACF11 parsing sucks on structs and member functions + var xmlRoot = arguments.xml[ arguments.root ]; + // Populate memento from XML + xmlRoot + .XMLChildren + .each( function( item ){ + memento[ item.XMLName ] = trim( item.XMLText ); + } ); + + return populateFromStruct( argumentCollection=arguments ); + } + + /** + * Populate a named or instantiated instance from a Query object + * + * @target The target to populate + * @qry The query to populate the object with + * @rowNumber The row number to use for population, defaults to 1 + * @scope Use scope injection instead of setters population. Ex: scope=variables.instance. + * @trustedSetter If set to true, the setter method will be called even if it does not exist in the bean + * @include A list of keys to include in the population + * @exclude A list of keys to exclude in the population + * @ignoreEmpty Ignore empty values on populations, great for ORM population + * @nullEmptyInclude A list of keys to NULL when empty + * @nullEmptyExclude A list of keys to NOT NULL when empty + * @composeRelationships Automatically attempt to compose relationships from memento + * + * @return The target populated with the packet + */ + function populateFromQuery( + required target, + required query qry, + numeric rowNumber="1", + string scope="", + boolean trustedSetter=false, + string include="", + string exclude="", + boolean ignoreEmpty=false, + string nullEmptyInclude="", + string nullEmptyExclude="", + boolean composeRelationships=false + ){ + arguments.memento = structnew(); + listToArray( arguments.qry.columnList ) + .each( function( item ){ + memento[ item ] = qry[ item ][ rowNumber ]; + } ); + + //populate bean and return + return populateFromStruct( argumentCollection=arguments ); + } + + /** + * Populate a named or instantiated instance from a Query object using a column prefix + * + * @target The target to populate + * @qry The query to populate the object with + * @prefix The prefix used to filter, Example: 'user_' would apply to the following columns: 'user_id' and 'user_name' but not 'address_id'. + * @rowNumber The row number to use for population, defaults to 1 + * @scope Use scope injection instead of setters population. Ex: scope=variables.instance. + * @trustedSetter If set to true, the setter method will be called even if it does not exist in the bean + * @include A list of keys to include in the population + * @exclude A list of keys to exclude in the population + * @ignoreEmpty Ignore empty values on populations, great for ORM population + * @nullEmptyInclude A list of keys to NULL when empty + * @nullEmptyExclude A list of keys to NOT NULL when empty + * @composeRelationships Automatically attempt to compose relationships from memento + * + * @return The target populated with the packet + */ + function populateFromQueryWithPrefix( + required target, + required query qry, + required string prefix, + numeric rowNumber="1", + string scope="", + boolean trustedSetter=false, + string include="", + string exclude="", + boolean ignoreEmpty=false, + string nullEmptyInclude="", + string nullEmptyExclude="", + boolean composeRelationships=false + ){ + var prefixLength = len( arguments.prefix ); + + arguments.memento = structnew(); + listToArray( arguments.qry.columnList ) + .filter( function( item ){ + return ( left( item, prefixLength ) == prefix ); + } ) + .each( function( item ){ + var trueColumnName = item.replaceNocase( prefix, "" ); + memento[ trueColumnName ] = qry[ item ][ rowNumber ]; + } ); + + //populate bean and return + return populateFromStruct( argumentCollection=arguments ); + } + + /** + * Populate a named or instantiated instance from a struct object using a key prefix + * + * @target The target to populate + * @memento The structure to populate the target with + * @prefix The prefix used to filter, Example: 'user_' would apply to the following columns: 'user_id' and 'user_name' but not 'address_id'. + * @scope Use scope injection instead of setters population. Ex: scope=variables.instance. + * @trustedSetter If set to true, the setter method will be called even if it does not exist in the bean + * @include A list of keys to include in the population + * @exclude A list of keys to exclude in the population + * @ignoreEmpty Ignore empty values on populations, great for ORM population + * @nullEmptyInclude A list of keys to NULL when empty + * @nullEmptyExclude A list of keys to NOT NULL when empty + * @composeRelationships Automatically attempt to compose relationships from memento + * + * @return The target populated with the packet + */ + function populateFromStructWithPrefix( + required target, + required struct memento, + required string prefix, + string scope="", + boolean trustedSetter=false, + string include="", + string exclude="", + boolean ignoreEmpty=false, + string nullEmptyInclude="", + string nullEmptyExclude="", + boolean composeRelationships=false + ){ + var prefixLength = len( arguments.prefix ); + var newMemento = {}; + + arguments.memento + .filter( function( key, value ){ + return( left( key, prefixLength ) == prefix ); + } ) + .each( function( key, value ){ + newMemento[ key.replaceNoCase( prefix, "" ) ] = value; + } ); + + //populate bean and return + arguments.memento = newMemento; + return populateFromStruct( argumentCollection=arguments ); + } + + /** + * Populate a named or instantiated instance from a struct object using a key prefix + * + * @target The target to populate + * @memento The structure to populate the target with + * @scope Use scope injection instead of setters population. Ex: scope=variables.instance. + * @trustedSetter If set to true, the setter method will be called even if it does not exist in the bean + * @include A list of keys to include in the population + * @exclude A list of keys to exclude in the population + * @ignoreEmpty Ignore empty values on populations, great for ORM population + * @nullEmptyInclude A list of keys to NULL when empty + * @nullEmptyExclude A list of keys to NOT NULL when empty + * @composeRelationships Automatically attempt to compose relationships from memento + * + * @return The target populated with the packet + */ + function populateFromStruct( + required target, + required struct memento, + string scope="", + boolean trustedSetter=false, + string include="", + string exclude="", + boolean ignoreEmpty=false, + string nullEmptyInclude="", + string nullEmptyExclude="", + boolean composeRelationships=false + ){ + var beanInstance = arguments.target; + var key = ""; + var pop = true; + var scopeInjection = false; + var udfCall = ""; + var args = ""; + var nullValue = false; + var propertyValue = ""; + var relationalMeta = ""; + + try{ + + // Determine Method of population + if( structKeyExists(arguments,"scope") and len(trim(arguments.scope)) neq 0 ){ + scopeInjection = true; + mixerUtil.start( beanInstance ); + } + + // If composing relationships, get target metadata + if( arguments.composeRelationships ) { + relationalMeta = getRelationshipMetaData( arguments.target ); + } + + // Populate Bean + for(key in arguments.memento){ + // init population flag + pop = true; + // init nullValue flag and shortcut to property value + // conditional with StructKeyExist, to prevent language issues with Null value checking of struct keys in ACF + if ( structKeyExists( arguments.memento, key) ){ + nullValue = false; + propertyValue = arguments.memento[ key ]; + + } else { + nullValue = true; + propertyValue = JavaCast( "null", "" ); + } + + // Include List? + if( len(arguments.include) AND NOT listFindNoCase(arguments.include,key) ){ + pop = false; + } + // Exclude List? + if( len(arguments.exclude) AND listFindNoCase(arguments.exclude,key) ){ + pop = false; + } + // Ignore Empty? Check added for real Null value + if( arguments.ignoreEmpty and not IsNull(propertyValue) and isSimpleValue(arguments.memento[key]) and not len( trim( arguments.memento[key] ) ) ){ + pop = false; + } + + // Pop? + if( pop ){ + // Scope Injection? + if( scopeInjection ){ + beanInstance.populatePropertyMixin(propertyName=key,propertyValue=propertyValue,scope=arguments.scope); + } + // Check if setter exists, evaluate is used, so it can call on java/groovy objects + else if( structKeyExists( beanInstance, "set" & key ) or arguments.trustedSetter ){ + // top-level null settings + if( arguments.nullEmptyInclude == "*" ) { + nullValue = true; + } + if( arguments.nullEmptyExclude == "*" ) { + nullValue = false; + } + // Is property in empty-to-null include list? + if( ( len( arguments.nullEmptyInclude ) && listFindNoCase( arguments.nullEmptyInclude, key ) ) ) { + nullValue = true; + } + // Is property in empty-to-null exclude list, or is exclude list "*"? + if( ( len( arguments.nullEmptyExclude ) AND listFindNoCase( arguments.nullEmptyExclude, key ) ) ){ + nullValue = false; + } + // Is value nullable (e.g., simple, empty string)? If so, set null... + // short circuit evealuaton of IsNull added, so it won't break IsSimpleValue with Real null values. Real nulls are already set. + if( !IsNull(propertyValue) && isSimpleValue( propertyValue ) && !len( trim( propertyValue ) ) && nullValue ) { + propertyValue = JavaCast( "null", "" ); + } + + var getEntityMap = function(){ + if( find( "2018", server.coldfusion.productVersion ) ){ + return arrayToList( ORMGetSessionFactory().getMetaModel().getAllEntityNames() ).listToArray(); + } else { + return structKeyArray( ORMGetSessionFactory().getAllClassMetadata() ); + } + }; + + // If property isn't null, try to compose the relationship + if( !isNull( propertyValue ) && composeRelationships && structKeyExists( relationalMeta, key ) ) { + // get valid, known entity name list + var validEntityNames = getEntityMap(); + var targetEntityName = ""; + /** + * The only info we know about the relationships are the property names and the cfcs + * CFC setting can be relative, so can't assume that component lookup will work + * APPROACH + * 1.) Easy: If property name of relationship is a valid entity name, use that + * 2.) Harder: If property name is not a valid entity name (e.g., one-to-many, many-to-many), use cfc name + * 3.) Nuclear: If neither above works, try by component meta data lookup. Won't work if using relative paths!!!! + */ + + // 1.) name match + if( validEntityNames.findNoCase( key ) ){ + targetEntityName = key; + } + // 2.) attempt match on CFC metadata + else if( validEntityNames.findNoCase( listLast( relationalMeta[ key ].cfc, "." ) ) ) { + targetEntityName = listLast( relationalMeta[ key ].cfc, "." ); + } + // 3.) component lookup + else { + try { + targetEntityName = getComponentMetaData( relationalMeta[ key ].cfc ).entityName; + } + catch( any e ) { + throw(type="BeanPopulator.PopulateBeanException", + message="Error populating bean #getMetaData(beanInstance).name# relationship of #key#. The component #relationalMeta[ key ].cfc# could not be found.", + detail="#e.Detail#
#e.message#
#e.tagContext.toString()#"); + } + + } + // if targetEntityName was successfully found + if( len( targetEntityName) ) { + // array or struct type (one-to-many, many-to-many) + if( listContainsNoCase( "one-to-many,many-to-many", relationalMeta[ key ].fieldtype ) ) { + // Support straight-up lists and convert to array + if( isSimpleValue( propertyValue ) ) { + propertyValue = listToArray( propertyValue ); + } + var relType = structKeyExists( relationalMeta[ key ], "type" ) && relationalMeta[ key ].type != "any" ? relationalMeta[ key ].type : 'array'; + var manyMap = reltype=="struct" ? {} : []; + // loop over array + for( var relValue in propertyValue ) { + // for type of array + if( relType=="array" ) { + // add composed relationship to array + arrayAppend( manyMap, EntityLoadByPK( targetEntityName, relValue ) ); + } + // for type of struct + else { + // make sure structKeyColumn is defined in meta + if( structKeyExists( relationalMeta[ key ], "structKeyColumn" ) ) { + // load the value + var item = EntityLoadByPK( targetEntityName, relValue ); + var structKeyColumn = relationalMeta[ key ].structKeyColumn; + var keyValue = ""; + // try to get struct key value from entity + if( !isNull( item ) ) { + try { + keyValue = invoke( item, "get#structKeyColumn#" ); + } + catch( Any e ) { + throw( + type = "BeanPopulator.PopulateBeanException", + message = "Error populating bean #getMetaData( beanInstance ).name# relationship of #key#. The structKeyColumn #structKeyColumn# could not be resolved.", + detail = "#e.Detail#
#e.message#
#e.tagContext.toString()#"); + } + } + // if the structKeyColumn value was found... + if( len( keyValue ) ) { + manyMap[ keyValue ] = item; + } + } + } + } + // set main property value to the full array of entities + propertyValue = manyMap; + } + // otherwise, simple value; load relationship (one-to-one, many-to-one) + else { + if( isSimpleValue( propertyValue ) && trim( propertyValue ) != "" ) { + propertyValue = EntityLoadByPK( targetEntityName, propertyValue ); + } + } + } // if target entity name found + } + // Populate the property as a null value + if( isNull( propertyValue ) ) { + // Finally...set the value + invoke( beanInstance, "set#key#", [ JavaCast( 'null', '' ) ] ); + } + // Populate the property as the value obtained whether simple or related + else { + invoke( beanInstance, "set#key#", [ propertyValue ] ); + } + + } // end if setter or scope injection + }// end if prop ignored + + }//end for loop + return beanInstance; + } + catch( Any e ){ + if( isNull( propertyValue ) ) { + arguments.keyTypeAsString = "NULL"; + } + else if ( isObject( propertyValue ) OR isCustomFunction( propertyValue )){ + arguments.keyTypeAsString = getMetaData( propertyValue ).name; + } + else{ + arguments.keyTypeAsString = propertyValue.getClass().toString(); + } + throw( + type = "BeanPopulator.PopulateBeanException", + message = "Error populating bean #getMetaData( beanInstance ).name# with argument #key# of type #arguments.keyTypeAsString#.", + detail = "#e.Detail#
#e.message#
#e.tagContext.toString()#" + ); + } + } + + /** + * Prepares a structure of target relational meta data + * + * @target The target to work on + */ + private struct function getRelationshipMetaData( required target ){ + var meta = {}; + // get array of properties + var stopRecursions= [ "lucee.Component", "WEB-INF.cftags.component" ]; + // Collect property metadata + variables.util + .getInheritedMetaData( arguments.target, stopRecursions ) + .properties + .filter( function( item ){ + return ( + item.keyExists( "fieldType" ) && + item.keyExists( "name" ) && + !listFindNoCase( "id,column", item.fieldtype ) + ); + } ) + .each( function( item ){ + meta[ item.name ] = item; + } ); + + return meta; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/core/dynamic/MixerUtil.cfc b/src/cfml/system/_wirebox/system/core/dynamic/MixerUtil.cfc new file mode 100644 index 000000000..443dd7487 --- /dev/null +++ b/src/cfml/system/_wirebox/system/core/dynamic/MixerUtil.cfc @@ -0,0 +1,234 @@ + + + + + + + + instance = structnew(); + instance.mixins = StructNew(); + + // Place our methods on the mixins struct + instance.mixins[ "removeMixin" ] = variables.removeMixin; + instance.mixins[ "injectMixin" ] = variables.injectMixin; + instance.mixins[ "invokerMixin" ] = variables.invokerMixin; + instance.mixins[ "injectPropertyMixin" ] = variables.injectPropertyMixin; + instance.mixins[ "removePropertyMixin" ] = variables.removePropertyMixin; + instance.mixins[ "populatePropertyMixin" ] = variables.populatePropertyMixin; + instance.mixins[ "includeitMixin" ] = variables.includeitMixin; + instance.mixins[ "getPropertyMixin" ] = variables.getPropertyMixin; + instance.mixins[ "exposeMixin" ] = variables.exposeMixin; + instance.mixins[ "methodProxy" ] = variables.methodProxy; + instance.mixins[ "getVariablesMixin" ] = variables.getVariablesMixin; + + return this; + + + + + + + + + + if ( NOT structKeyExists( arguments.CFC, "$mixed" ) ){ + for( var thisUDF in instance.mixins ){ + arguments.CFC[ thisUDF ] = instance.mixins[ thisUDF ]; + } + arguments.CFC.$mixed = true; + } + + + + + + + + for( var udf in instance.mixins ){ + structDelete( arguments.CFC, udf ); + } + structDelete( arguments.CFC, "$mixed" ); + + + + + + + + + + + // get new name + if( !len( arguments.newName ) ){ + arguments.newName = arguments.method; + } + + // stash it away + if( !structKeyExists( this, "$exposedMethods") ){ + this.$exposedMethods = {}; + } + this.$exposedMethods[ arguments.method ] = variables[ arguments.method ]; + + // replace with proxy. + this[ arguments.newName ] = this.methodProxy; + + // Create alias if needed + if( arguments.newName != arguments.method ){ + this.$exposedMethods[ arguments.newName ] = this.$exposedMethods[ arguments.method ]; + } + + return this; + + + + + + + var methodName = getFunctionCalledName(); + + if( !structKeyExists( this.$exposedMethods, methodName ) ){ + throw( + message = "The exposed method you are calling: #methodName# does not exist", + detail = "Exposed methods are #structKeyList( this.$exposedMethods )#", + type = "ExposedMethodProxy" + ); + } + + var method = this.$exposedMethods[ methodName ]; + return method( argumentCollection=arguments ); + + + + + + + + + + + + + + + + + + + + + variables[ arguments.name ] = arguments.UDF; + this[ arguments.name ] = arguments.UDF; + + return this; + + + + + + + + + + // Validate Property + if( structKeyExists( evaluate( arguments.scope ), arguments.propertyName ) ){ + "#arguments.scope#.#arguments.propertyName#" = arguments.propertyValue; + } + + return this; + + + + + + + + + + var thisScope = variables; + if( arguments.scope eq "this"){ thisScope = this; } + + if( NOT structKeyExists(thisScope,arguments.name) AND structKeyExists(arguments,"default")){ + return arguments.default; + } + + return thisScope[arguments.name]; + + + + + + + + + + "#arguments.scope#.#arguments.propertyName#" = arguments.propertyValue; + + return this; + + + + + + + + structDelete( this, arguments.udfName ); + structDelete( variables, arguments.udfName ); + + return this; + + + + + + + + + structDelete( evaluate( arguments.scope ), arguments.propertyName ); + + return this; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/core/events/EventPool.cfc b/src/cfml/system/_wirebox/system/core/events/EventPool.cfc new file mode 100644 index 000000000..44dfbe7e9 --- /dev/null +++ b/src/cfml/system/_wirebox/system/core/events/EventPool.cfc @@ -0,0 +1,130 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * This object models an event driven pool of objects +*/ +component accessors="true"{ + + /** + * The collection of listeners in the pool backed by a linked hashmap which is synchornized for threading + */ + property name="pool" doc_generic="java.util.LinkedHashMap"; + + /** + * The event pool state name + */ + property name="state"; + + /** + * Constructor + * + * @state The name of the pool + */ + function init( required state ){ + var linkedHashMap = createObject( "java", "java.util.LinkedHashMap" ).init( 5 ); + var collections = createObject( "java", "java.util.Collections" ); + + // Create the event pool, start with 5 instead of 16 to save space + variables.pool = collections.synchronizedMap( linkedHashMap ); + variables.state = arguments.state; + + return this; + } + + /** + * Stupid accessors in CF11 does not work. + */ + function getPool(){ + return variables.pool; + } + + /** + * Register an object with this pool + * + * @key The key of the object + * @target The object + * + * @return EventPool + */ + function register( required key, required target ){ + variables.pool.put( lcase( arguments.key ), arguments.target ); + return this; + } + + /** + * Unregister an object from this pool + * + * @key The key of the object + */ + boolean function unregister( required key ){ + arguments.key = lcase( arguments.key ); + if( structKeyExists( variables.pool, arguments.key ) ){ + variables.pool.remove( arguments.key ); + return true; + } + return false; + } + + /** + * Check if a key exists in the pool + */ + boolean function exists( required key ){ + return structKeyExists( variables.pool, lcase( arguments.key ) ); + } + + /** + * Get an object from this event pool. Else return a blank structure if not found + */ + function getObject( required key ){ + arguments.key = lcase( arguments.key ); + if( structKeyExists( variables.pool, arguments.key ) ){ + return variables.pool[ arguments.key ]; + } + return {}; + } + + /** + * Process this event pool according to it's name. + * + * @interceptData The data used in the interception call + * @interceptData.doc_generic struct + * + * @return EventPool + */ + function process( required interceptData ){ + // Loop and execute each target object as registered in order + for( var key in variables.pool ){ + // Invoke the execution point + var stopChain = invoker( variables.pool[ key ], arguments.interceptData ); + + // Check for results + if( stopChain ){ break; } + } + + return this; + } + + /** + * Execute the interception point, returns a value if the chain should be stopped (true) or ignored (void/false) + * + * @target The target object + * @interceptData The data used in the interception call + * @interceptData.doc_generic struct + * + */ + private function invoker( required target, required interceptData ){ + var results = invoke( + arguments.target, + variables.state, + { interceptData = arguments.interceptData } + ); + + if( !isNull( results ) && isBoolean( results ) ){ + return results; + } + + return false; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/core/events/EventPoolManager.cfc b/src/cfml/system/_wirebox/system/core/events/EventPoolManager.cfc new file mode 100644 index 000000000..16afe0917 --- /dev/null +++ b/src/cfml/system/_wirebox/system/core/events/EventPoolManager.cfc @@ -0,0 +1,253 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * A basic event pool manager for observed event pools. This event manager will manage 1 or more event pools. + * The manager will inspect target objects for implemented functions and match them to event states. + * However, if a function has the metadata attribute of 'observe=true' on it, then it will also add it + * as a custom state +*/ +component accessors="true"{ + + /** + * Event states to listen for + */ + property name="eventStates"; + + /** + * Stop recurssion classes + */ + property name="stopRecursionClasses"; + + /** + * Pool container struct + */ + property name="eventPoolContainer" type="struct"; + + /** + * Constructor + * + * @eventStates The event states to listen for + * @stopRecursionClasses The classes (comma-delim) to not inspect for events + */ + function init( required array eventStates, stopRecursionClasses="" ){ + // Setup properties of the event manager + variables.eventStates = arguments.eventStates; + variables.stopRecursionClasses = arguments.stopRecursionClasses; + + // class id code + variables.classID = createObject( "java", "java.lang.System" ).identityHashCode( this ); + + // Init event pool container + variables.eventPoolContainer = structnew(); + + return this; + } + + /** + * Process a state announcement. If the state does not exist it will ignore it. + * + * @state The state to process + * @interceptData The data to pass into the interception event + * + * @return EventPoolManager + */ + function processState( required state, struct interceptData={} ){ + if( variables.eventPoolContainer.keyExists( arguments.state ) ){ + variables.eventPoolContainer + .find( arguments.state ) + .process( arguments.interceptData ); + } + + return this; + } + + /** + * Register an object in an event pool. If the target object is already in a state, it will not be added again. + * The object get's inspected for registered states or you can even send custom states in. + * Also, you can annotate the methods in the target object with 'observe=true' and we will register that state also. + * + * @target The target object to register in an event pool + * @name The name to use when registering the object. If not passed, the name will be used from the object's metadata + * @customStates A comma delimmited list of custom states, if the object or class sent in observes them + * + * @return EventPoolManager + */ + function register( required target, name="", customStates="" ){ + var md = getMetadata( arguments.target ); + + // Check if name sent? If not, get the name from the last part of its name + if( NOT len( trim( arguments.name ) ) ){ + arguments.name = listLast( md.name, "." ); + } + + lock name="EventPoolManager.#variables.classID#.RegisterObject.#arguments.name#" type="exclusive" throwontimeout="true" timeout="30"{ + // Append Custom Statess + appendInterceptionPoints( arguments.customStates ); + + // Register this target's event observation states with its appropriate interceptor/observation state + parseMetadata( md, {} ) + .each( function( item ){ + registerInEventState( name, item, target ); + } ); + } + + return this; + } + + /** + * Register an object with a specified event observation state. + * + * @key The key to use when storing the object + * @state The event state pool to save the object in + * @target The object to register + * + * @return EventPoolManager + */ + function registerInEventState( required key, required state, required target ){ + var eventPool = ""; + + // Verify if the event state doesn't exist in the evnet pool, else create it + if ( not structKeyExists( variables.eventPoolContainer, arguments.state ) ){ + // Create new event pool + eventPool = new wirebox.system.core.events.EventPool( arguments.state ); + // Register it with this pool manager + variables.eventPoolContainer[ arguments.state ] = eventPool; + } else { + // Get the State we need to register in + eventPool = variables.eventPoolContainer[ arguments.state ]; + } + + // Verify if the target object is already in the state + if( NOT eventPool.exists( arguments.key ) ){ + // Register it + eventPool.register( arguments.key, arguments.target ); + } + + return this; + } + + /** + * Get an object from the pool + * + * @name The name of the object + * + * @throws EventPoolManager.ObjectNotFound + */ + function getObject( required name ){ + for( var key in variables.eventPoolContainer ){ + if( structFind( variables.eventPoolContainer, key ).exists( arguments.name ) ){ + return structFind( variables.eventPoolContainer, key ).getObject( arguments.name ); + } + } + + // Throw Exception + throw( + message = "Object: #arguments.name# not found in any event pool state: #structKeyList( variables.eventPoolContainer )#.", + type = "EventPoolManager.ObjectNotFound" + ); + } + + /** + * Append a list of custom interception points to the CORE interception points and returns the points + * + * @customStates A comma delimmited list or array of custom interception states to append. If they already exists, then they will not be added again. + * + * @return The current interception points + */ + array function appendInterceptionPoints( required customStates ){ + + // Inflate custom points + if( isSimpleValue( arguments.customStates ) ){ + arguments.customStates = listToArray( arguments.customStates ); + } + + for( var thisPoint in arguments.customStates ){ + if( !arrayFindNoCase( variables.eventStates, thisPoint ) ){ + variables.eventStates.append( thisPoint ); + } + } + + return variables.eventStates; + } + + /** + * Get an event pool by state name, if not found, it returns an empty structure + * + * @state The state to retrieve + */ + function getEventPool( required state ){ + if( variables.eventPoolContainer.keyExists( arguments.state ) ){ + return variables.eventPoolContainer[ arguments.state ]; + } + return {}; + } + + /** + * Unregister an object form an event pool state. If no event state is passed, then we will unregister the object from ALL the pools the object exists in. + * + * @name The name of the object to unregister + * @state The state to unregister from. If not passed, then we will unregister from ALL pools + */ + boolean function unregister( required name, state="" ){ + var unregistered = false; + + // Unregister the object + for( var key in variables.eventPoolContainer ){ + if( len( arguments.state ) eq 0 OR arguments.state eq key ){ + structFind( variables.eventPoolContainer, key ).unregister( arguments.name ); + unregistered = true; + } + } + + return unregistered; + } + + /** + * I get a component's valid observation states for registration. + */ + private struct function parseMetadata( required metadata, required struct eventsFound ){ + // Register local functions + if( structKeyExists( arguments.metadata, "functions" ) ){ + + for( var thisFunction in arguments.metadata.functions ){ + + // Verify observe annotation + if( thisFunction.keyExists( "interceptionPoint" ) ){ + // Register the observation point just in case + appendInterceptionPoints( thisFunction.name ); + } + + // verify it's an observation state and Not Registered already + if( + arrayFindNoCase( variables.eventStates, thisFunction.name ) + && + !arguments.eventsFound.keyExists( thisFunction.name ) + ){ + // Observation Event Found + arguments.eventsFound[ thisFunction.name ] = true; + } + } + + } + + // Start Registering inheritances? + if ( structKeyExists( arguments.metadata, "extends") + AND + NOT listFindNoCase( getStopRecursionClasses(), arguments.metadata.extends.name ) + ){ + parseMetadata( arguments.metadata.extends, arguments.eventsFound ); + } + + // return the event states found + return arguments.eventsFound; + } + + /** + * Get ColdBox utility object + */ + private function getUtil(){ + return new wirebox.system.core.util.Util(); + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/core/util/CFMLEngine.cfc b/src/cfml/system/_wirebox/system/core/util/CFMLEngine.cfc new file mode 100644 index 000000000..6b3fb2b1f --- /dev/null +++ b/src/cfml/system/_wirebox/system/core/util/CFMLEngine.cfc @@ -0,0 +1,69 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* Allows you to maninpulate determine CFML engine capabilities +*/ +component { + + //setup the engine properties + this.ADOBE = "ADOBE"; + this.LUCEE = "LUCEE"; + + // JDK Version + this.JDK_VERSION = createObject( "java", "java.lang.System" ).getProperty( "java.version" ); + + /** + * Constructor + */ + function init() { + // Feature map + variables.features = { + adobe = {}, + lucee = {} + }; + + return this; + + } + +// ------------------------------------------- PUBLIC ------------------------------------------- + + /** + * Returns the current running CFML major version + */ + numeric function getVersion() { + return listfirst( server.coldfusion.productversion ); + } + + /** + * Returns the current running CFML full version + */ + string function getFullVersion() { + return server.coldfusion.productversion; + } + + /** + * Get the current CFML Engine + */ + string function getEngine() { + var engine = this.adobe; + + if ( server.coldfusion.productname eq "Lucee" ){ + engine = this.lucee; + } + + return engine; + } + + /** + * Feature Active Check + * + * @feature The feature to check + * @engine The engine we are checking + */ + boolean function featureCheck( required feature, required engine ) { + return variables.features[ arguments.engine ][ arguments.feature ]; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/core/util/CFMappingHelper.cfc b/src/cfml/system/_wirebox/system/core/util/CFMappingHelper.cfc new file mode 100644 index 000000000..008310a05 --- /dev/null +++ b/src/cfml/system/_wirebox/system/core/util/CFMappingHelper.cfc @@ -0,0 +1,21 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* Allows you to maninpulate CF mappings +*/ +component{ + + /** + * Add a ColdFusion mapping + * + * @name The name of the mapping + * @path The path of the mapping + */ + CFMappingHelper function addMapping( required string name, required string path ){ + var appSettings = getApplicationMetadata(); + appSettings.mappings[ arguments.name ] = arguments.path; + return this; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/core/util/LuceeMappingHelper.cfc b/src/cfml/system/_wirebox/system/core/util/LuceeMappingHelper.cfc new file mode 100644 index 000000000..d878c09cf --- /dev/null +++ b/src/cfml/system/_wirebox/system/core/util/LuceeMappingHelper.cfc @@ -0,0 +1,23 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* Creation of mappings via Lucee +*/ +component{ + + /** + * Add a Lucee mapping + * + * @name The name of the mapping + * @path The path of the mapping + */ + LuceeMappingHelper function addMapping( required name, required path ) { + var mappings = getApplicationSettings().mappings; + mappings[ arguments.name ] = arguments.path; + application action='update' mappings='#mappings#'; + + return this; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/core/util/Util.cfc b/src/cfml/system/_wirebox/system/core/util/Util.cfc new file mode 100644 index 000000000..c1ae5898a --- /dev/null +++ b/src/cfml/system/_wirebox/system/core/util/Util.cfc @@ -0,0 +1,382 @@ + + + + + + + if( structKeyExists( variables, "mixerUtil" ) ){ + return variables.mixerUtil; + } + variables.mixerUtil = new wirebox.system.core.dynamic.MixerUtil(); + return variables.mixerUtil; + + + + + + + + return arguments.in.reduce( function( result, item, index ){ + var target = {}; + if( !isNull( result ) ){ + target = result; + } + target[ index ] = item; + return target; + } ); + + + + + + + + return getFileInfo( getAbsolutePath( arguments.filename ) ).lastModified; + + + + + + + + + + + + + + if( fileExists( arguments.path ) ){ + return arguments.path; + } + return expandpath( arguments.path ); + + + + + + + var engine = "ADOBE"; + + if ( server.coldfusion.productname eq "Lucee" ){ engine = "LUCEE"; } + + switch( engine ){ + case "ADOBE" : { + if( findNoCase( "cfthread", createObject( "java", "java.lang.Thread" ).currentThread().getThreadGroup().getName() ) ){ + return true; + } + break; + } + case "LUCEE" : { + + var version = listFirst( server.lucee.version, "." ); + + if( version == 5 ){ + return isInThread(); + } + + if( findNoCase( "cfthread", createObject( "java", "java.lang.Thread" ).currentThread().getThreadGroup().getName() ) ){ + return true; + } + break; + } + } //end switch statement. + + return false; + + + + + + + + + var returnString = arguments.str; + var regex = "\$\{([0-9a-z\-\.\_]+)\}"; + var lookup = 0; + var varName = 0; + var varValue = 0; + + // Loop and Replace + while( true ){ + // Search For Pattern + var lookup = reFindNocase( regex, returnString, 1, true ); + // Found? + if( lookup.pos[ 1 ] ){ + //Get Variable Name From Pattern + var varName = mid( returnString, lookup.pos[ 2 ], lookup.len[ 2 ] ); + var varValue = "VAR_NOT_FOUND"; + + // Lookup Value + if( structKeyExists( arguments.settings, varname ) ){ + varValue = arguments.settings[ varname ]; + } + // Lookup Nested Value + else if( isDefined( "arguments.settings.#varName#" ) ){ + varValue = structFindKey( arguments.settings, varName )[ 1 ].value; + } + // Remove PlaceHolder Entirely + returnString = removeChars( returnString, lookup.pos[ 1 ], lookup.len[ 1 ] ); + // Insert Var Value + returnString = insert(varValue, returnString, lookup.pos[ 1 ] - 1 ); + } else { + break; + } + } + + return returnString; + + + + + + + + + var value = getJavaSystem().getProperty( arguments.key ); + if ( ! isNull( value ) ) { + return value; + } + + value = getJavaSystem().getEnv( arguments.key ); + if ( ! isNull( value ) ) { + return value; + } + + if ( ! isNull( arguments.defaultValue ) ) { + return arguments.defaultValue; + } + + throw( + type = "SystemSettingNotFound", + message = "Could not find a Java System property or Env setting with key [#arguments.key#]." + ); + + + + + + + + + var value = getJavaSystem().getProperty( arguments.key ); + if ( ! isNull( value ) ) { + return value; + } + + if ( ! isNull( arguments.defaultValue ) ) { + return arguments.defaultValue; + } + + throw( + type = "SystemSettingNotFound", + message = "Could not find a Java System property with key [#arguments.key#]." + ); + + + + + + + + + var value = getJavaSystem().getEnv( arguments.key ); + if ( ! isNull( value ) ) { + return value; + } + + if ( ! isNull( arguments.defaultValue ) ) { + return arguments.defaultValue; + } + + throw( + type = "SystemSettingNotFound", + message = "Could not find a environment variable with key [#arguments.key#]." + ); + + + + + + + if ( ! structKeyExists( variables, "javaSystem" ) ) { + variables.javaSystem = createObject( "java", "java.lang.System" ); + } + return variables.javaSystem; + + + + + + + + + + + var familyPath = ""; + + switch( arguments.family ){ + case "handler" : { familyPath = "wirebox.system.EventHandler"; break; } + case "interceptor" : { familyPath = "wirebox.system.Interceptor"; break; } + default:{ + throw( 'Invalid family sent #arguments.family#' ); + } + } + + return isInstanceOf( arguments.target, familyPath ); + + + + + + + + + var familyPath = ""; + + switch( arguments.family ){ + case "handler" : { familyPath = "wirebox.system.EventHandler"; break; } + case "interceptor" : { familyPath = "wirebox.system.Interceptor"; break; } + default:{ + throw( 'Invalid family sent #arguments.family#' ); + } + } + + // Mix it up baby + arguments.target.$injectUDF = getMixerUtil().injectMixin; + + // Create base family object + var baseObject = createObject( "component", familyPath ); + + // Check if init already exists? + if( structKeyExists( arguments.target, "init" ) ){ + arguments.target.$cbInit = baseObject.init; + } + + // Mix in methods + for( var key in baseObject ){ + // If handler has overriden method, then don't override it with mixin, simulated inheritance + if( NOT structKeyExists( arguments.target, key ) ){ + arguments.target.$injectUDF( key, baseObject[ key ] ); + } + } + + // Mix in fake super class + arguments.target.$super = baseObject; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + // Try to find a match + for( var thisClass in arguments.stopRecursions ){ + if( compareNoCase( thisClass, arguments.classname) eq 0 ){ + return true; + } + } + return false; + + + + + + + + + var mappingHelper = ""; + + // Detect server + if( listFindNoCase( "Lucee", server.coldfusion.productname ) ) { + mappingHelper = new LuceeMappingHelper(); + } else { + mappingHelper = new CFMappingHelper(); + } + + // Add / registration + if( left( arguments.name, 1 ) != "/" ){ + arguments.name = "/#arguments.name#"; + } + + // Add mapping + mappingHelper.addMapping( arguments.name, arguments.path ); + + return this; + + + + diff --git a/src/cfml/system/_wirebox/system/ioc/Builder.cfc b/src/cfml/system/_wirebox/system/ioc/Builder.cfc new file mode 100644 index 000000000..df1aeb3eb --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/Builder.cfc @@ -0,0 +1,754 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * The WireBox builder for components, java, etc. I am in charge of building stuff and integration dsl builders. + **/ + component serializable="false" accessors="true"{ + + /** + * Injector Reference + */ + property name="injector"; + + /** + * LogBox reference + */ + property name="logBox"; + + /** + * Logging utilty object + */ + property name="log"; + + /** + * ColdBox Utility + */ + property name="utility"; + + /** + * Custom DSL Map Storage + */ + property name="customDSL" type="struct"; + + /** + * ColdBox DSL Utility + */ + property name="coldboxDSL"; + + /** + * CacheBox DSL Utility + */ + property name="cacheBoxDSL"; + + /** + * LogBox DSL Utility + */ + property name="logBoxDSL"; + + /** + * Constructor. If called without a configuration binder, then WireBox will instantiate the default configuration binder found wirebox.system.ioc.config.DefaultBinder + * + * @injector The linked WireBox injector + * @injector.doc_generic wirebox.system.ioc.Injector + * + * @return wirebox.system.ioc.Builder + */ + Builder function init( required injector ){ + + variables.injector = arguments.injector; + variables.logBox = arguments.injector.getLogBox(); + variables.log = arguments.injector.getLogBox().getlogger( this ); + variables.utility = arguments.injector.getUtil(); + variables.customDSL = {}; + + // Do we need to build the coldbox DSL namespace + if( variables.injector.isColdBoxLinked() ){ + variables.coldboxDSL = new wirebox.system.ioc.dsl.ColdBoxDSL( arguments.injector ); + } + // Is CacheBox Linked? + if( variables.injector.isCacheBoxLinked() ){ + variables.cacheBoxDSL = new wirebox.system.ioc.dsl.CacheBoxDSL( arguments.injector ); + } + // Build LogBox DSL Namespace + variables.logBoxDSL = new wirebox.system.ioc.dsl.LogBoxDSL( arguments.injector ); + + return this; + } + + /** + * Register custom DSL builders with this main wirebox builder + * + */ + Builder function registerCustomBuilders(){ + var customDSL = variables.injector.getBinder().getCustomDSL(); + + // Register Custom DSL Builders + for( var key in customDSL ){ + registerDSL( namespace=key, path=customDSL[ key ] ); + } + return this; + } + + /** + * A direct way of registering custom DSL namespaces + * + * @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: wirebox.system.ioc.dsl.IDSLBuilder + */ + Builder function registerDSL( required namespace, required path ){ + // register dsl + variables.customDSL[ arguments.namespace ] = new "#arguments.path#"( variables.injector ); + // Debugging + if( variables.log.canDebug() ){ + variables.log.debug( "Registered custom DSL Builder with namespace: #arguments.namespace#" ); + } + return this; + } + + /** + * Used to provider providers via mixers on targeted objects + */ + function buildProviderMixer(){ + var targetInjector = this.$wbScopeStorage.get( this.$wbScopeInfo.key, this.$wbScopeInfo.scope ); + var targetProvider = this.$wbProviders[ getFunctionCalledName() ]; + + // Verify if this is a mapping first? + if( targetInjector.containsInstance( targetProvider ) ){ + return targetInjector.getInstance( name=targetProvider, targetObject=this ); + } + + // else treat as full DSL + return targetInjector.getInstance( dsl=targetProvider, targetObject=this ); + } + + /** + * Build a cfc class via mappings + * + * @mapping The mapping to construct + * @mapping.doc_generic wirebox.system.ioc.config.Mapping + * @initArguments The constructor structure of arguments to passthrough when initializing the instance + * @initArguments.doc_generic struct + */ + function buildCFC( required mapping, initArguments = structNew() ){ + var thisMap = arguments.mapping; + var oModel = createObject( "component", thisMap.getPath() ); + + // Do we have virtual inheritance? + if( arguments.mapping.isVirtualInheritance() ){ + // retrieve the VI mapping. + var viMapping = variables.injector.getBinder().getMapping( arguments.mapping.getVirtualInheritance() ); + // Does it match the family already? + if( NOT isInstanceOf( oModel, viMapping.getPath() ) ){ + // Virtualize it. + toVirtualInheritance( viMapping, oModel, arguments.mapping ); + } + } + + // Constructor initialization? + if( thisMap.isAutoInit() AND structKeyExists( oModel, thisMap.getConstructor() ) ){ + // Get Arguments + var constructorArgs = buildArgumentCollection( thisMap, thisMap.getDIConstructorArguments(), oModel ); + + // Do We have initArguments to override + if( NOT structIsEmpty( arguments.initArguments ) ){ + structAppend( constructorArgs, arguments.initArguments, true ); + } + + try { + // Invoke constructor + invoke( oModel, thisMap.getConstructor(), constructorArgs ); + } catch( any e ){ + throw( + type = "Builder.BuildCFCDependencyException", + message = "Error building: #thisMap.getName()# -> #e.message#.", + detail = "DSL: #thisMap.getDSL()#, Path: #thisMap.getPath()#, Error Location: #e.tagContext[ 1 ].template#:#e.tagContext[ 1 ].line#" + ); + } + } + + return oModel; + } + + /** + * Build an object using a factory method + * + * @mapping The mapping to construct + * @mapping.doc_generic wirebox.system.ioc.config.Mapping + * @initArguments The constructor structure of arguments to passthrough when initializing the instance + * @initArguments.doc_generic struct + */ + function buildFactoryMethod( required mapping, initArguments=structNew() ){ + var thisMap = arguments.mapping; + var factoryName = thisMap.getPath(); + + // check if factory exists, else throw exception + if( NOT variables.injector.containsInstance( factoryName ) ){ + throw( + message = "The factory mapping: #factoryName# is not registered with the injector", + type = "Builder.InvalidFactoryMappingException" + ); + } + + // get Factory mapping + var oFactory = variables.injector.getInstance( factoryName ); + // Get Method Arguments + var methodArgs = buildArgumentCollection( thisMap, thisMap.getDIMethodArguments(), oFactory ); + // Do we have overrides + if( NOT structIsEmpty( arguments.initArguments ) ){ + structAppend( methodArgs, arguments.initArguments, true ); + } + + // Get From Factory + var oModel = invoke( oFactory, thisMap.getMethod(), methodArgs ); + + //Return factory bean + return oModel; + } + + /** + * Build a Java class via mappings + * + * @mapping The mapping to construct + * @mapping.doc_generic wirebox.system.ioc.config.Mapping + */ + function buildJavaClass( required mapping ){ + var DIArgs = arguments.mapping.getDIConstructorArguments(); + var args = []; + var thisMap = arguments.mapping; + + // Process arguments to constructor call. + for( var thisArg in DIArgs ){ + if( !isNull( thisArg.javaCast ) ){ + args.append( javaCast( thisArg.javacast, thisArg.value ) ); + } else { + args.append( thisArg.value ); + } + } + + // init? + if( thisMap.isAutoInit() ){ + if( args.len() ){ + return invoke( + createObject( "java", arguments.mapping.getPath() ), + "init", + args + ); + } + return createObject( "java", arguments.mapping.getPath() ).init(); + } + + // return with no init + return createObject( "java", arguments.mapping.getPath() ); + } + + /** + * Build arguments for a mapping and return the structure representation + * + * @mapping The mapping to construct + * @mapping.doc_generic wirebox.system.ioc.config.Mapping + * @argumentArray The argument array of data + * @targetObject The target object we are building the DSL dependency for + */ + function buildArgumentCollection( required mapping, required argumentArray, required targetObject ){ + var thisMap = arguments.mapping; + var DIArgs = arguments.argumentArray; + var args = {}; + + // Process Arguments + for( var thisArg in DIArgs ){ + + // Process if we have a value and continue + if( !isNull( thisArg.value ) ){ + args[ thisArg.name ] = thisArg.value; + continue; + } + + // Is it by DSL construction? If so, add it and continue, if not found it returns null, which is ok + if( !isNull( thisArg.dsl ) ){ + args[ thisArg.name ] = buildDSLDependency( definition=thisArg, targetID=thisMap.getName(), targetObject=arguments.targetObject ); + continue; + } + + // If we get here then it is by ref id, so let's verify it exists and optional + if( variables.injector.containsInstance( thisArg.ref ) ){ + args[ thisArg.name ] = variables.injector.getInstance( name=thisArg.ref ); + continue; + } + + // Not found, so check if it is required + if( thisArg.required ){ + // Log the error + variables.log.error( "Target: #thisMap.getName()# -> Argument reference not located: #thisArg.name#", thisArg ); + // not found but required, then throw exception + throw( + message = "Argument reference not located: #thisArg.name#", + detail = "Injecting: #thisMap.getName()#. The argument details are: #thisArg.toString()#.", + type = "Injector.ArgumentNotFoundException" + ); + } // else just log it via debug + else if( variables.log.canDebug() ){ + variables.log.debug( "Target: #thisMap.getName()# -> Argument reference not located: #thisArg.name#", thisArg ); + } + + } + + return args; + } + + /** + * Build a webservice object + * + * @mapping The mapping to construct + * @mapping.doc_generic wirebox.system.ioc.config.Mapping + * @initArguments The constructor structure of arguments to passthrough when initializing the instance + * @initArguments.doc_generic struct + */ + function buildWebservice( required mapping, initArguments={} ){ + var argStruct = {}; + var DIArgs = arguments.mapping.getDIConstructorArguments(); + + // Process args + for( var thisArg in DIArgs ){ + argStruct[ thisArg.name ] = thisArg.value; + } + + // Do we ahve overrides + if( NOT structIsEmpty( arguments.initArguments ) ){ + structAppend( argStruct, arguments.initArguments, true ); + } + + return createObject( "webservice", arguments.mapping.getPath(), argStruct ); + } + + /** + * Build an rss feed the WireBox way + * + * @mapping The mapping to construct + * @mapping.doc_generic wirebox.system.ioc.config.Mapping + */ + function buildFeed( required mapping ){ + var results = {}; + + cffeed( + action = "read", + source = arguments.mapping.getPath(), + query = "results.items", + properties = "results.metadata", + timeout = "20" + ); + + return results; + } + + // Internal DSL Builders + + /** + * Build a DSL Dependency using a simple dsl string + * + * @dsl The dsl string to build + * @targetID The target ID we are building this dependency for + * @targetObject The target object we are building the DSL dependency for + */ + function buildSimpleDSL( required dsl, required targetID, required targetObject = "" ){ + var definition = { + required = true, + name = "", + dsl = arguments.dsl + }; + return buildDSLDependency( + definition = definition, + targetID = arguments.targetID, + targetObject = arguments.targetObject + ); + } + + /** + * Build a DSL Dependency, if not found, returns null + * + * @definition The dependency definition structure: name, dsl as keys + * @targetID The target ID we are building this dependency for + * @targetObject The target object we are building the DSL dependency for + */ + function buildDSLDependency( required definition, required targetID, targetObject = "" ){ + var refLocal = {}; + var DSLNamespace = listFirst( arguments.definition.dsl, ":" ); + + // Check if Custom DSL exists, if it does, execute it + if( structKeyExists( variables.customDSL, DSLNamespace ) ){ + return variables.customDSL[ DSLNamespace ].process( argumentCollection=arguments ); + } + + // Determine Type of Injection according to type + // Some namespaces requires the ColdBox context, if not found, an exception is thrown. + switch( DSLNamespace ){ + + // ColdBox Context DSL + case "coldbox" : { + refLocal.dependency = variables.coldboxDSL.process( argumentCollection=arguments ); + break; + } + + // CacheBox Context DSL + case "cacheBox" : { + // check if linked + if( !variables.injector.isCacheBoxLinked() AND !variables.injector.isColdBoxLinked() ){ + throw( + message = "The DSLNamespace: #DSLNamespace# cannot be used as it requires a ColdBox/CacheBox Context", + type = "Builder.IllegalDSLException" + ); + } + // retrieve it + refLocal.dependency = variables.cacheBoxDSL.process( argumentCollection=arguments ); + break; + } + + // logbox injection DSL always available + case "logbox" : { + refLocal.dependency = variables.logBoxDSL.process( argumentCollection=arguments ); + break; + } + + // WireBox Internal DSL for models and id + case "model" : case "id" : { + refLocal.dependency = getModelDSL( argumentCollection=arguments ); + break; + } + + // provider injection DSL always available + case "provider" : { + refLocal.dependency = getProviderDSL( argumentCollection=arguments ); + break; + } + + // wirebox injection DSL always available + case "wirebox" : { + refLocal.dependency = getWireBoxDSL( argumentCollection=arguments ); + break; + } + + // java class + case "java" : { + refLocal.dependency = getJavaDSL( argumentCollection=arguments ); + break; + } + + // coldfusion type annotation + case "bytype" : { + refLocal.dependency = getByTypeDSL( argumentCollection=arguments ); + break; + } + + // If no DSL's found, let's try to use the name as the empty namespace + default : { + if( len( DSLNamespace ) && left( DSLNamespace, 1 ) == "@" ){ + arguments.definition.dsl = arguments.definition.name & arguments.definition.dsl; + } + refLocal.dependency = getModelDSL( argumentCollection=arguments ); + } + } + + // return only if found + if( structKeyExists( refLocal, "dependency" ) ){ + return refLocal.dependency; + } + + // was dependency required? If so, then throw exception + if( arguments.definition.required ){ + // Logging + if( variables.log.canError() ){ + variables.log.error( "Target: #arguments.targetID# -> DSL Definition: #arguments.definition.toString()# did not produce any resulting dependency" ); + } + + // Throw exception as DSL Dependency requested was not located + throw( + message = "The DSL Definition #arguments.definition.toString()# did not produce any resulting dependency", + detail = "The target requesting the dependency is: '#arguments.targetID#'", + type = "Builder.DSLDependencyNotFoundException" + ); + } + // else return void, no dependency found that was required + } + + // INTERNAL DSL BUILDER METHODS + + /** + * Get a Java object + * + * @definition The dependency definition structure: name, dsl as keys + * @targetObject The target object we are building the DSL dependency for + */ + private any function getJavaDSL( required definition, targetObject ){ + var javaClass = getToken( arguments.definition.dsl, 2, ":" ); + + return createObject( "java", javaClass ); + } + + /** + * Get dependencies using the wirebox dependency DSL + * + * @definition The dependency definition structure: name, dsl as keys + * @targetObject The target object we are building the DSL dependency for + */ + private any function getWireBoxDSL( required definition, targetObject ){ + var thisType = arguments.definition.dsl; + var thisTypeLen = listLen(thisType,":" ); + var thisLocationType = ""; + var thisLocationKey = ""; + + // DSL stages + switch( thisTypeLen ){ + // WireBox injector + case 1 : { return variables.injector; } + + // Level 2 DSL + case 2 : { + thisLocationKey = getToken( thisType, 2, ":" ); + switch( thisLocationKey ){ + case "parent" : { return variables.injector.getParent(); } + case "eventManager" : { return variables.injector.getEventManager(); } + case "binder" : { return variables.injector.getBinder(); } + case "populator" : { return variables.injector.getObjectPopulator(); } + case "properties" : { return variables.injector.getBinder().getProperties(); } + } + break; + } + + // Level 3 DSL + case 3 : { + thisLocationType = getToken( thisType, 2, ":" ); + thisLocationKey = getToken( thisType, 3, ":" ); + // DSL Level 2 Stage Types + switch( thisLocationType ){ + // Scope DSL + case "scope" : { return variables.injector.getScope( thisLocationKey ); break; } + case "property" : { return variables.injector.getBinder().getProperty( thisLocationKey );break; } + } + break; + } // end level 3 main DSL + } + } + + /** + * Get dependencies using the model dependency DSL + * + * @definition The dependency definition structure: name, dsl as keys + * @targetObject The target object we are building the DSL dependency for + */ + private any function getModelDSL( required definition, targetObject ){ + var thisType = arguments.definition.dsl; + var thisTypeLen = listLen( thisType, ":" ); + var methodCall = ""; + var modelName = ""; + + // DSL stages + switch( thisTypeLen ){ + // No injection defined, use property name: property name='luis' inject; + case 0 : { + modelName = arguments.definition.name; + break; + } + // Injected defined, can be different scenarios + // property name='luis' inject="id"; use property name + // property name='luis' inject="model"; use property name + // property name='luis' inject="alias"; + case 1 : { + // Are we the key identifiers + if( listFindNoCase( "id,model", arguments.definition.dsl ) ){ + modelName = arguments.definition.name; + } + // else we are a real ID + else { + modelName = arguments.definition.dsl; + } + + break; + } + // model:{alias} stage + case 2 : { + modelName = getToken( thisType, 2, ":" ); + break; + } + // model:{alias}:{method} stage + case 3 : { + modelName = getToken( thisType, 2, ":" ); + methodCall = getToken( thisType, 3, ":" ); + break; + } + } + + // Check if model Exists + if( variables.injector.containsInstance( modelName ) ){ + // Get Model object + var oModel = variables.injector.getInstance( modelName ); + // Factories: TODO: Add arguments with 'ref()' parsing for argument references or 'dsl()' + if( len( methodCall ) ){ + return invoke( oModel, methodCall ); + } + return oModel; + } else if ( variables.log.canDebug() ){ + variables.log.debug( "getModelDSL() cannot find model object #modelName# using definition #arguments.definition.toString()#" ); + } + } + + /** + * Get dependencies using the our provider pattern DSL + * + * @definition The dependency definition structure + * @targetObject The target object we are building the DSL dependency for. If empty, means we are just requesting building + */ + private any function getProviderDSL( required definition, targetObject="" ){ + var thisType = arguments.definition.dsl; + var thisTypeLen = listLen( thisType,":" ); + var providerName = ""; + + // DSL stages + switch( thisTypeLen ){ + // provider default, get name of the provider from property + case 1: { providerName = arguments.definition.name; break; } + // provider:{name} stage + case 2: { providerName = getToken( thisType, 2, ":" ); break; } + // multiple stages then most likely it is a full DSL being used + default : { + providerName = replaceNoCase( thisType, "provider:", "" ); + } + } + + // Build provider arguments + var args = { + scopeRegistration = variables.injector.getScopeRegistration(), + scopeStorage = variables.injector.getScopeStorage(), + targetObject = arguments.targetObject + }; + + // Check if the passed in provider is an ID directly + if( variables.injector.containsInstance( providerName ) ){ + args.name = providerName; + } + // Else try to tag it by FULL DSL + else{ + args.dsl = providerName; + } + + // Build provider and return it. + return createObject( "component","wirebox.system.ioc.Provider" ).init( argumentCollection=args ); + } + + /** + * Get dependencies using the mapped type + * + * @definition The dependency definition structure + * @targetObject The target object we are building the DSL dependency for. If empty, means we are just requesting building + */ + private any function getByTypeDSL( required definition, targetObject ){ + var injectType = arguments.definition.type; + + if( variables.injector.containsInstance( injectType ) ){ + return variables.injector.getInstance( injectType ); + } + } + + /** + * Do our virtual inheritance magic + * + * @mapping The mapping to convert to + * @target The target object + * @targetMapping The target mapping + * + * @return The target object + */ + function toVirtualInheritance( required mapping, required target, required targetMapping ){ + var excludedProperties = "$super,$wbaopmixed,$mixed,this,init"; + + // Check if the base mapping has been discovered yet + if( NOT arguments.mapping.isDiscovered() ){ + // process inspection of instance + arguments.mapping.process( + binder = variables.injector.getBinder(), + injector = variables.injector + ); + } + // Build it out now and wire it + var baseObject = variables.injector.buildInstance( arguments.mapping ); + variables.injector.autowire( target=baseObject, mapping=arguments.mapping ); + + // Mix them up baby! + variables.utility.getMixerUtil().start( arguments.target ); + variables.utility.getMixerUtil().start( baseObject ); + + // Check if init already exists in target and base? + if( structKeyExists( arguments.target, "init" ) AND structKeyExists( baseObject, "init" ) ){ + arguments.target.$superInit = baseObject.init; + } + + // Mix in public methods and public properties + for( var key in baseObject ){ + // If target has overriden method, then don't override it with mixin, simulated inheritance + if( NOT structKeyExists( arguments.target, key ) AND NOT listFindNoCase( excludedProperties, key ) ){ + // inject method in both variables and this scope to simulate public access + arguments.target.injectMixin( key, baseObject[ key ] ); + } + } + + // Prepare for private Property/method Injections + var targetVariables = arguments.target.getVariablesMixin(); + var generateAccessors = false; + if( arguments.mapping.getObjectMetadata().keyExists( "accessors" ) and arguments.mapping.getObjectMetadata().accessors ){ + generateAccessors = true; + } + var baseProperties = {}; + + // Process baseProperties lookup map + if( arguments.mapping.getObjectMetadata().keyExists( "properties" ) ){ + arguments.mapping.getObjectMetadata().properties + .each( function( item ){ + baseProperties[ item.name ] = true; + } ); + } + + baseObject.getVariablesMixin() + // filter out overrides + .filter( function( key, value ) { + return ( !targetVariables.keyExists( key ) AND NOT listFindNoCase( excludedProperties, key ) ); + } ) + .each( function( propertyName, propertyValue ){ + // inject the property/method now + target.injectPropertyMixin( propertyName, propertyValue ); + // Do we need to do automatic generic getter/setters + if( generateAccessors and baseProperties.keyExists( propertyName ) ){ + + if( ! structKeyExists( target, "get#propertyName#" ) ){ + target.injectMixin( "get" & propertyName, variables.genericGetter ); + } + + if( ! structKeyExists( target, "set#propertyName#" ) ){ + target.injectMixin( "set" & propertyName, variables.genericSetter ); + } + + } + } ); + + // Mix in virtual super class + arguments.target.$super = baseObject; + + return arguments.target; + } + + /** + * Generic setter for Virtual Inheritance + */ + private function genericSetter() { + var propName = getFunctionCalledName().replaceNoCase( 'set', '' ); + variables[ propName ] = arguments[ 1 ]; + return this; + } + + /** + * Generic getter for Virtual Inheritance + */ + private function genericGetter() { + var propName = getFunctionCalledName().replaceNoCase( 'get', '' ); + return variables[ propName ]; + } + +} diff --git a/src/cfml/system/_wirebox/system/ioc/IInjector.cfc b/src/cfml/system/_wirebox/system/ioc/IInjector.cfc new file mode 100644 index 000000000..9db35a6f9 --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/IInjector.cfc @@ -0,0 +1,50 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * An interface that enables any CFC to act like a parent injector within WireBox. + **/ + interface { + + /** + * Link a parent Injector with this injector and return itself + * + * @injector A WireBox Injector to assign as a parent to this Injector + * @injector.doc_generic wirebox.system.ioc.Injector + * + * @return IInjector + */ + function setParent( required injector ); + + /** + * Get a reference to the parent injector instance, else an empty simple string meaning nothing is set + * + * @return wirebox.system.ioc.Injector + */ + function getParent(); + + /** + * Locates, Creates, Injects and Configures an object model instance + * + * @name The mapping name or CFC instance path to try to build up + * @dsl The dsl string to use to retrieve the instance model object, mutually exclusive with 'name' + * @initArguments The constructor structure of arguments to passthrough when initializing the instance + * @targetObject The object requesting the dependency, usually only used by DSL lookups + */ + function getInstance( name, dsl, struct initArguments, targetObject="" ); + + /** + * Checks if this injector can locate a model instance or not + * + * @name The object name or alias to search for if this container can locate it or has knowledge of it + */ + boolean function containsInstance( required name ); + + /** + * Shutdown the injector gracefully by calling the shutdown events internally + * + * @return IInjector + */ + function shutdown(); + + } \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/ioc/IProvider.cfc b/src/cfml/system/_wirebox/system/ioc/IProvider.cfc new file mode 100644 index 000000000..748528d66 --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/IProvider.cfc @@ -0,0 +1,13 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * A WireBox provider object that retrieves objects by using the provider pattern. + **/ +interface { + + /** + * Get the provided object + */ + public any function get(); +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/ioc/Injector.cfc b/src/cfml/system/_wirebox/system/ioc/Injector.cfc new file mode 100644 index 000000000..1609a98e0 --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/Injector.cfc @@ -0,0 +1,1190 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * The WireBox injector is the pivotal class in WireBox that performs dependency injection. + * It can be used standalone or it can be used in conjunction of a ColdBox application context. + * It can also be configured with a mapping configuration file called a binder, that can provide object/mappings and configuration data. + * + * A WireBox Injector: Builds the graphs of objects that make up your application. + * + * Easy Startup: + *
+ * injector = new wirebox.system.ioc.Injector();
+ * 
+ * + * Binder Startup + *
+ * injector = new wirebox.system.ioc.Injector(new MyBinder());
+ * 
+ * + * Binder Path Startup + *
+ * injector = new wirebox.system.ioc.Injector( "config.MyBinder" );
+ * 
+ */ +component serializable="false" accessors="true" implements="wirebox.system.ioc.IInjector"{ + + /** + * Java System + */ + property name="javaSystem"; + + /** + * ColdBox Utility class + */ + property name="utility"; + + /** + * Scope Storages Utility + */ + property name="scopeStorage"; + + /** + * WireBox Version + */ + property name="version"; + + /** + * The Configuration Binder object + */ + property name="binder"; + + /** + * ColdBox Application Link + */ + property name="coldbox"; + + /** + * CacheBox Link + */ + property name="cacheBox"; + + /** + * Event Manager Link + */ + property name="eventManager"; + + /** + * Configured Event States + */ + property name="eventStates" type="array"; + + /** + * LogBox and Class Logger + */ + property name="logBox"; + + /** + * Log Reference + */ + property name="log"; + + /** + * Parent Injector + */ + property name="parent"; + + /** + * LifeCycle Scopes + */ + property name="scopes"; + + /** + * The injector Unique ID + */ + property name="injectorID"; + + /** + * Constructor. If called without a configuration binder, then WireBox will instantiate the default configuration binder found in: wirebox.system.ioc.config.DefaultBinder + * + * @binder The WireBox binder or data CFC instance or instantiation path to configure this injector with + * @properties A structure of binding properties to passthrough to the Binder Configuration CFC + * @properties.doc_generic struct + * @coldbox A coldbox application context that this instance of WireBox can be linked to, if not using it, we just ignore it. + * @coldbox.doc_generic wirebox.system.web.Controller + **/ + Injector function init( + binder="wirebox.system.ioc.config.DefaultBinder", + struct properties=structNew(), + coldbox="" + ){ + // Setup Available public scopes + this.SCOPES = new wirebox.system.ioc.Scopes(); + // Setup Available public types + this.TYPES = new wirebox.system.ioc.Types(); + + // Do we have a binder? + if( isSimpleValue( arguments.binder ) AND NOT len( trim( arguments.binder ) ) ){ + arguments.binder = "wirebox.system.ioc.config.DefaultBinder"; + } + + // Java System + variables.javaSystem = createObject( 'java', 'java.lang.System' ); + // Utility class + variables.utility = new wirebox.system.core.util.Util(); + // Scope Storages + variables.scopeStorage = new wirebox.system.core.collections.ScopeStorage(); + // Version + variables.version = "5.1.4+741"; + // The Configuration Binder object + variables.binder = ""; + // ColdBox Application Link + variables.coldbox = ""; + // LogBox Link + variables.logBox = ""; + // CacheBox Link + variables.cacheBox = ""; + // Event Manager Link + variables.eventManager = ""; + // Configured Event States + variables.eventStates = [ + "afterInjectorConfiguration", // X once injector is created and configured + "beforeInstanceCreation", // X Before an injector creates or is requested an instance of an object, the mapping is passed. + "afterInstanceInitialized", // X once the constructor is called and before DI is performed + "afterInstanceCreation", // X once an object is created, initialized and done with DI + "beforeInstanceInspection", // X before an object is inspected for injection metadata + "afterInstanceInspection", // X after an object has been inspected and metadata is ready to be saved + "beforeInjectorShutdown", // X right before the shutdown procedures start + "afterInjectorShutdown", // X right after the injector is shutdown + "beforeInstanceAutowire", // X right before an instance is autowired + "afterInstanceAutowire" // X right after an instance is autowired + ]; + // LogBox and Class Logger + variables.logBox = ""; + variables.log = ""; + // Parent Injector + variables.parent = ""; + // LifeCycle Scopes + variables.scopes = {}; + + // Prepare instance ID + variables.injectorID = variables.javaSystem.identityHashCode( this ); + // Prepare Lock Info + variables.lockName = "WireBox.Injector.#variables.injectorID#"; + // Link ColdBox Context if passed + variables.coldbox = arguments.coldbox; + + // Configure the injector for operation + configure( arguments.binder, arguments.properties ); + + return this; + } + + /** + * Configure this injector for operation, called by the init(). You can also re-configure this injector programmatically, but it is not recommended. + * + * @binder The configuration binder object or path to configure this Injector instance with + * @binder.doc_generic wirebox.system.ioc.config.Binder + * @properties A structure of binding properties to passthrough to the Configuration CFC + * @properties.doc_generic struct + **/ + Injector function configure( required binder, required struct properties ){ + var iData = {}; + var withColdbox = isColdBoxLinked(); + + // Lock For Configuration + lock name=variables.lockName type="exclusive" timeout="30" throwontimeout="true"{ + if( withColdBox ){ + // link LogBox + variables.logBox = variables.coldbox.getLogBox(); + // Configure Logging for this injector + variables.log = variables.logBox.getLogger( this ); + // Link CacheBox + variables.cacheBox = variables.coldbox.getCacheBox(); + // Link Event Manager + variables.eventManager = variables.coldbox.getInterceptorService(); + } + + // Create and Configure Event Manager + configureEventManager(); + + // Store binder object built accordingly to our binder building procedures + variables.binder = buildBinder( arguments.binder, arguments.properties ); + + // Create local cache, logging and event management if not coldbox context linked. + if( NOT withColdbox ){ + // Running standalone, so create our own logging first + configureLogBox( variables.binder.getLogBoxConfig() ); + // Create local CacheBox reference + configureCacheBox( variables.binder.getCacheBoxConfig() ); + } + + // Register All Custom Listeners + registerListeners(); + // Create our object builder + variables.builder = new wirebox.system.ioc.Builder( this ); + // Register Custom DSL Builders + variables.builder.registerCustomBuilders(); + // Register Life Cycle Scopes + registerScopes(); + // Parent Injector declared + if( isObject( variables.binder.getParentInjector() ) ){ + setParent( variables.binder.getParentInjector() ); + } + + // Scope registration if enabled? + if( variables.binder.getScopeRegistration().enabled ){ + doScopeRegistration(); + } + + // Register binder as an interceptor + if( NOT isColdBoxLinked() ){ + variables.eventManager.register( variables.binder, "wirebox-binder" ); + } else { + 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. + variables.binder.processMappings(); + + // Announce To Listeners we are online + iData.injector = this; + variables.eventManager.processState( "afterInjectorConfiguration", iData ); + } + + return this; + } + + /** + * Shutdown the injector gracefully by calling the shutdown events internally. + **/ + function shutdown(){ + var iData = { + injector = this + }; + + // Log + if( variables.log.canInfo() ){ + variables.log.info( "Shutdown of Injector: #getInjectorID()# requested and started." ); + } + // Notify Listeners + variables.eventManager.processState( "beforeInjectorShutdown", iData ); + + // Check if binder has onShutdown convention + if( structKeyExists( variables.binder, "onShutdown" ) ){ + variables.binder.onShutdown(); + } + + // Is parent linked + if( isObject( variables.parent ) ){ + variables.parent.shutdown(); + } + + // standalone cachebox? Yes, then shut it down baby! + if( isCacheBoxLinked() ){ + variables.cacheBox.shutdown(); + } + + // Remove from scope + removeFromScope(); + + // Notify Listeners + variables.eventManager.processState( "afterInjectorShutdown", iData ); + + // Log shutdown complete + if( variables.log.canInfo() ){ + variables.log.info( "Shutdown of injector: #getInjectorID()# completed." ); + } + + return this; + } + + /** + * Locates, Creates, Injects and Configures an object model instance + * + * @name The mapping name or CFC instance path to try to build up + * @dsl The dsl string to use to retrieve the instance model object, mutually exclusive with 'name + * @initArguments The constructor structure of arguments to passthrough when initializing the instance + * @initArguments.doc_generic struct + * @targetObject The object requesting the dependency, usually only used by DSL lookups + **/ + function getInstance( name, dsl, struct initArguments = structNew(), targetObject="" ){ + // Get by DSL? + if( structKeyExists( arguments, "dsl" ) ){ + return variables.builder.buildSimpleDSL( + dsl = arguments.dsl, + targetID = "ExplicitCall", + targetObject = arguments.targetObject + ); + } + + // Check if Mapping Exists? + if( NOT variables.binder.mappingExists( arguments.name ) ){ + // No Mapping exists, let's try to locate it first. We are now dealing with request by conventions + var instancePath = locateInstance( arguments.name ); + + // check if not found and if we have a parent factory + if( NOT len( instancePath ) AND isObject( variables.parent ) ){ + // we do have a parent factory so just request it from there, let the hierarchy deal with it + return variables.parent.getInstance( argumentCollection=arguments ); + } + + // If Empty Throw Exception + if( NOT len( instancePath ) ){ + variables.log.error( "Requested instance:#arguments.name# was not located in any declared scan location(s): #structKeyList(variables.binder.getScanLocations())# or full CFC path" ); + throw( + message = "Requested instance not found: '#arguments.name#'", + detail = "The instance could not be located in any declared scan location(s) (#structKeyList(variables.binder.getScanLocations())#) or full path location", + type = "Injector.InstanceNotFoundException" + ); + } + // Let's create a mapping for this requested convention name+path as it is the first time we see it + registerNewInstance( arguments.name, instancePath ); + } + + // Get Requested Mapping (Guaranteed to exist now) + var mapping = variables.binder.getMapping( arguments.name ); + + // Check if the mapping has been discovered yet, and if it hasn't it must be autowired enabled in order to process. + if( NOT mapping.isDiscovered() ){ + try { + // process inspection of instance + mapping.process( binder=variables.binder, injector=this ); + } catch( any e ) { + // Remove bad mapping + var mappings = variables.binder.getMappings(); + mappings.delete( name ); + // rethrow + throw( object=e ); + } + } + + // scope persistence check + if( NOT structKeyExists( variables.scopes, mapping.getScope() ) ){ + variables.log.error( "The mapping scope: #mapping.getScope()# is invalid and not registered in the valid scopes: #structKeyList( variables.scopes )#" ); + throw( + message = "Requested mapping scope: #mapping.getScope()# is invalid for #mapping.getName()#", + detail = "The registered valid object scopes are #structKeyList(variables.scopes)#", + type = "Injector.InvalidScopeException" ); + } + + // Request object from scope now, we now have it from the scope created, initialized and wired + var target = variables + .scopes[ mapping.getScope() ] + .getFromScope( mapping, arguments.initArguments ); + + // Announce creation, initialization and DI magicfinicitation! + variables.eventManager.processState( + "afterInstanceCreation", + { mapping=mapping, target=target, injector=this } + ); + + return target; + } + + /** + * Build an instance, this is called from registered scopes only as they provide locking and transactions + * + * @mapping The mapping to construct + * @mapping.doc_generic wirebox.system.ioc.config.Mapping + * @initArguments The constructor structure of arguments to passthrough when initializing the instance + * @initArguments.doc_generic struct + **/ + function buildInstance( required mapping, struct initArguments = {} ){ + var thisMap = arguments.mapping; + // before construction event + variables.eventManager.processState( + "beforeInstanceCreation", + { mapping=arguments.mapping, injector=this } + ); + + var oModel = ""; + // determine construction type + switch( thisMap.getType() ){ + case "cfc" : { + oModel = variables.builder.buildCFC( thisMap, arguments.initArguments ); + break; + } + case "java" : { + oModel = variables.builder.buildJavaClass( thisMap ); + break; + } + case "webservice" : { + oModel = variables.builder.buildWebservice( thisMap, arguments.initArguments ); + break; + } + case "constant" : { + oModel = thisMap.getValue(); + break; + } + case "rss" : { + oModel = variables.builder.buildFeed( thisMap ); + break; + } + case "dsl" : { + oModel = variables.builder.buildSimpleDSL( dsl=thisMap.getDSL(), targetID=thisMap.getName() ); + break; + } + case "factory" : { + oModel = variables.builder.buildFactoryMethod( thisMap, arguments.initArguments ); + break; + } + case "provider" : { + // verify if it is a simple value or closure/UDF + if( isSimpleValue( thisMap.getPath() ) ){ + oModel = getInstance( thisMap.getPath() ).get(); + } else { + var closure = thisMap.getPath(); + oModel = closure( injector = this ); + } + break; + } + default : { + throw( + message = "Invalid Construction Type: #thisMap.getType()#", + type = "Injector.InvalidConstructionType" + ); + } + } + + // Check and see if this mapping as an influence closure + var influenceClosure = thisMap.getInfluenceClosure(); + if( !isSimpleValue( influenceClosure ) ) { + // Influence the creation of the instance + var result = influenceClosure( instance=oModel, injector=this ); + // Allow the closure to override the entire instance if it wishes + if( !isNull( result ) ){ + oModel = result; + } + } + + // log data + if( variables.log.canDebug() ){ + variables.log.debug( "Instance object built: #arguments.mapping.getName()#:#arguments.mapping.getPath().toString()#" ); + } + + // announce afterInstanceInitialized + variables.eventManager.processState( + "afterInstanceInitialized", + { mapping=arguments.mapping, target=oModel, injector=this } + ); + + return oModel; + } + + /** + * Register a new requested mapping object instance thread safely and returns the mapping configured for this instance + * + * @name The name of the mapping to register + * @instancePath The path of the mapping to register + **/ + function registerNewInstance( required name, required instancePath ){ + // Register new instance mapping + lock name="Injector.#getInjectorID()#.RegisterNewInstance.#hash( arguments.instancePath )#" + type="exclusive" + timeout="20" + throwontimeout="true"{ + if( NOT variables.binder.mappingExists( arguments.name ) ){ + // create a new mapping to be registered within the binder + var mapping = new wirebox.system.ioc.config.Mapping( arguments.name ) + .setType( variables.binder.TYPES.CFC ) + .setPath( arguments.instancePath ); + // Now register it + variables.binder.setMapping( arguments.name, mapping ); + // return it + return mapping; + } + } + + return variables.binder.getMapping( arguments.name ); + } + + /** + * A direct way of registering custom DSL namespaces + * + * @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: wirebox.system.ioc.dsl.IDSLBuilder + */ + Injector function registerDSL( required namespace, required path ){ + variables.builder.registerDSL( argumentCollection=arguments ); + return this; + } + + /** + * Checks if this injector can locate a model instance or not + * + * @name The object name or alias to search for if this container can locate it or has knowledge of it + */ + boolean function containsInstance( required name ){ + // check if we have a mapping first + if( variables.binder.mappingExists( arguments.name ) ){ + return true; + } + // check if we can locate it? + if( locateInstance( arguments.name ).len() ){ + return true; + } + // Ask parent hierarchy if set + if( isObject( variables.parent ) ){ + return variables.parent.containsInstance( arguments.name ); + } + + // Else NADA! + return false; + } + + /** + * Tries to locate a specific instance by scanning all scan locations and returning the instantiation path. If model not found then the returned instantiation path will be empty + * + * @name The model instance name to locate + */ + function locateInstance( required name ){ + var scanLocations = variables.binder.getScanLocations(); + var CFCName = replace( arguments.name, ".", "/", "all" ) & ".cfc"; + + // Check Scan Locations In Order + for( var thisScanPath in scanLocations){ + // Check if located? If so, return instantiation path + if( fileExists( scanLocations[ thisScanPath ] & CFCName ) ){ + if( variables.log.canDebug() ){ variables.log.debug( "Instance: #arguments.name# located in #thisScanPath#" ); } + return thisScanPath & "." & arguments.name; + } + } + + // Not found, so let's do full namespace location + if( fileExists( expandPath( "/" & CFCName ) ) ){ + if( variables.log.canDebug() ){ variables.log.debug( "Instance: #arguments.name# located as is." ); } + return arguments.name; + } + + // debug info, NADA found! + if( variables.log.canDebug() ){ variables.log.debug( "Instance: #arguments.name# was not located anywhere" ); } + return ""; + } + + /** + * I wire up target objects with dependencies either by mappings or a-la-carte autowires + * + * @target The target object to wire up + * @mapping The object mapping with all the necessary wiring metadata. Usually passed by scopes and not a-la-carte autowires + * @mapping.doc_generic wirebox.system.ioc.config.Mapping + * @targetID A unique identifier for this target to wire up. Usually a class path or file path should do. If none is passed we will get the id from the passed target via introspection but it will slow down the wiring + * @annotationCheck This value determines if we check if the target contains an autowire annotation in the cfcomponent tag: autowire=true|false, it will only autowire if that metadata attribute is set to true. The default is false, which will autowire anything automatically. + * @annotationCheck.doc_generic Boolean + */ + function autowire( + required target, + mapping, + targetID="", + boolean annotationCheck=false + ){ + var targetObject = arguments.target; + var md = ""; + + // Do we have a mapping? Or is this a-la-carte wiring + if( NOT structKeyExists( arguments, "mapping" ) ){ + // Ok, a-la-carte wiring, let's get our id first + // Do we have an incoming target id? + if( NOT len( arguments.targetID ) ){ + // need to get metadata to verify identity + md = variables.utility.getInheritedMetaData( arguments.target, getBinder().getStopRecursions() ); + // We have identity now, use the full location path + arguments.targetID = md.path; + } + + // Now that we know we have an identity, let's verify if we have a mapping already + if( NOT variables.binder.mappingExists( arguments.targetID ) ){ + // No mapping found, means we need to map this object for the first time. + // Is md retreived? If not, retrieve it as we need to register it for the first time. + if( isSimpleValue( md ) ){ + md = variables.utility.getInheritedMetaData( arguments.target, getBinder().getStopRecursions() ); + } + // register new mapping instance + registerNewInstance( arguments.targetID, md.path ); + // get Mapping created + arguments.mapping = variables.binder.getMapping( arguments.targetID ); + // process it with current metadata + arguments.mapping.process( binder=variables.binder, injector=this, metadata=md ); + } else { + // get the mapping as it exists already + arguments.mapping = variables.binder.getMapping( arguments.targetID ); + } + }// end if mapping not found + + // Set local variable for easy reference use mapping to wire object up. + var thisMap = arguments.mapping; + if( NOT len( arguments.targetID ) ){ + arguments.targetID = thisMap.getName(); + } + + // Only autowire if no annotation check or if there is one, make sure the mapping is set for autowire, and this is a CFC + if ( thisMap.getType() eq this.TYPES.CFC + AND + ( ( arguments.annotationCheck eq false ) OR ( arguments.annotationCheck AND thisMap.isAutowire() ) ) + ){ + + // announce beforeInstanceAutowire + var iData = { + mapping = thisMap, + target = arguments.target, + targetID = arguments.targetID, + injector = this + }; + variables.eventManager.processState( "beforeInstanceAutowire", iData ); + + // prepare instance for wiring, done once for persisted objects and CFCs only + variables.utility.getMixerUtil().start( arguments.target ); + + // Bean Factory Awareness + if( structKeyExists( targetObject, "setBeanFactory" ) ){ + targetObject.setBeanFactory( this ); + } + if( structKeyExists( targetObject, "setInjector" ) ){ + targetObject.setInjector( this ); + } + // ColdBox Context Awareness + if( structKeyExists( targetObject, "setColdBox" ) ){ + targetObject.setColdBox( getColdBox() ); + } + // DIProperty injection + processInjection( targetObject, thisMap.getDIProperties(), arguments.targetID ); + // DISetter injection + processInjection( targetObject, thisMap.getDISetters(), arguments.targetID ); + // Process Provider Methods + processProviderMethods( targetObject, thisMap ); + // Process Mixins + processMixins( targetObject, thisMap ); + // Process After DI Complete + processAfterCompleteDI( targetObject, thisMap.getOnDIComplete() ); + + // After Instance Autowire + variables.eventManager.processState( "afterInstanceAutowire", iData ); + + // Debug Data + if( variables.log.canDebug() ){ + variables.log.debug( "Finalized Autowire for: #arguments.targetID#", thisMap.getMemento().toString() ); + } + } + } + + /** + * Link a parent Injector with this injector + * + * @injector A WireBox Injector to assign as a parent to this Injector + * @injector.doc_generic wirebox.system.ioc.Injector + * + * @return Injector + */ + function setParent( required injector ){ + variables.parent = arguments.injector; + return this; + } + + /** + * Get a reference to the parent injector instance, else an empty simple string meaning nothing is set + * + * @doc_generic wirebox.system.ioc.Injector + */ + function getParent() { + return variables.parent; + } + + /** + * Get an object populator useful for populating objects from JSON,XML, etc. + * + * @doc_generic wirebox.system.core.dynamic.BeanPopulator + */ + function getObjectPopulator() { + return new wirebox.system.core.dynamic.BeanPopulator(); + } + + /** + * Checks if Coldbox application context is linked + * + * @doc_generic boolean + */ + boolean function isColdBoxLinked() { + return isObject( variables.coldbox ); + } + + /** + * Checks if CacheBox is linked + * + * @doc_generic boolean + */ + boolean function isCacheBoxLinked() { + return isObject( variables.cacheBox ); + } + + /** + * Remove the Injector from scope registration if enabled, else does nothing + */ + Injector function removeFromScope() { + var scopeInfo = variables.binder.getScopeRegistration(); + // if enabled remove. + if( scopeInfo.enabled ){ + variables.scopeStorage.delete( scopeInfo.key, scopeInfo.scope ); + + // Log info + if( variables.log.canDebug() ){ + variables.log.debug( "Injector removed from scope: #scopeInfo.toString()#" ); + } + } + return this; + } + + /** + * Get a registered scope in this injector by name + * @scope The scope name + */ + function getScope( required any scope ){ + return variables.scopes[ arguments.scope ]; + } + + /** + * Clear the singleton cache + */ + Injector function clearSingletons() { + variables.scopes[ "SINGLETON" ].clear(); + return this; + } + + /** + * Return a self reference using the scoped registration, mostly used by providers or scope widening objects + * + * @doc_generic wirebox.system.ioc.Injector + */ + function locateScopedSelf() { + var scopeInfo = variables.binder.getScopeRegistration(); + + // Return if it exists, else throw exception + if( variables.scopeStorage.exists( scopeInfo.key, scopeInfo.scope ) ){ + return variables.scopeStorage.get( scopeInfo.key, scopeInfo.scope ); + } + + throw( + message = "The injector has not be registered in any scope", + detail = "The scope info is: #scopeInfo.toString()#", + type = "Injector.InvalidScopeRegistration" + ); + } + + /** + * Return the core util object + * + * @doc_generic wirebox.system.core.util.Util + */ + function getUtil() { + return variables.utility; + } + + /** + * Get the structure of scope registration information + */ + struct function getScopeRegistration(){ + return variables.binder.getScopeRegistration(); + } + + /****************************************** PRIVATE ************************************************/ + + /** + * Process mixins on the selected target + * + * @targetObject The target object to do some goodness on + * @mapping The target mapping + */ + private Injector function processMixins( required targetObject, required mapping ){ + var mixin = new wirebox.system.ioc.config.Mixin().$init( arguments.mapping.getMixins() ); + + // iterate and mixin baby! + for( var key in mixin ){ + if( key NEQ "$init" ){ + // add the provided method to the providers structure. + arguments.targetObject.injectMixin( name=key, UDF=mixin[ key ] ); + } + } + + return this; + } + + /** + * Process provider methods on the selected target + * + * @targetObject The target object to do some goodness on + * @mapping The target mapping + */ + private Injector function processProviderMethods( required targetObject, required mapping ){ + var providerMethods = arguments.mapping.getProviderMethods(); + var providerLen = arrayLen( providerMethods ); + var x = 1; + + // Decorate the target if provider methods found, in preparation for replacements + if( providerLen ){ + arguments.targetObject.$wbScopeInfo = getScopeRegistration(); + arguments.targetObject.$wbScopeStorage = variables.scopeStorage; + arguments.targetObject.$wbProviders = {}; + } + + // iterate and provide baby! + for( var x=1; x lte providerLen; x++ ){ + // add the provided method to the providers structure. + arguments.targetObject.$wbProviders[ providerMethods[ x ].method ] = providerMethods[ x ].mapping; + // Override the function by injecting it, this does private/public functions + arguments.targetObject.injectMixin( providerMethods[ x ].method, variables.builder.buildProviderMixer ); + } + + return this; + } + + /** + * Process after DI completion routines + * @targetObject The target object to do some goodness on + * @DICompleteMethods The array of DI completion methods to call + */ + private Injector function processAfterCompleteDI(required targetObject, required DICompleteMethods) { + var DILen = arrayLen(arguments.DICompleteMethods); + + // Check for convention first + if ( StructKeyExists( arguments.targetObject, "onDIComplete" ) ) { + // Call our mixin invoker + arguments.targetObject.invokerMixin( method="onDIComplete" ); + } + + // Iterate on DICompleteMethods + for( var thisMethod in arguments.DICompleteMethods ) { + if ( StructKeyExists( arguments.targetObject, thisMethod ) ) { + // Call our mixin invoker + arguments.targetObject.invokerMixin( method=thisMethod ); + } + } + + return this; + } + + /** + * Process property and setter injection + * + * @tagetObject The target object to do some goodness on + * @DIData The DI data to use + * @targetID The target ID to process injections + */ + private Injector function processInjection( required targetObject, required DIData, required targetID ){ + var DILen = arrayLen( arguments.DIData ); + + for( var x=1; x lte DILen; x++ ){ + var thisDIData = arguments.DIData[ x ]; + // Init the lookup structure + var refLocal = {}; + // Check if direct value has been placed. + if( !isNull( thisDIData.value ) ){ + refLocal.dependency = thisDIData.value; + } + // else check if dsl is used? + else if( !isNull( thisDIData.dsl ) ){ + // Get DSL dependency by sending entire DI structure to retrieve + refLocal.dependency = variables.builder.buildDSLDependency( + definition = thisDIData, + targetID = arguments.targetID, + targetObject = arguments.targetObject + ); + } + // else we have to have a reference ID or a nasty bug has ocurred + else{ + refLocal.dependency = getInstance( arguments.DIData[ x ].ref ); + } + + // Check if dependency located, else log it and skip + if( structKeyExists( refLocal, "dependency" ) ){ + // scope or setter determination + refLocal.scope = ""; + if( structKeyExists( arguments.DIData[ x ], "scope" ) ){ + refLocal.scope = arguments.DIData[ x ].scope; + } + // Inject dependency + injectTarget( + target = targetObject, + propertyName = arguments.DIData[ x ].name, + propertyObject = refLocal.dependency, + scope = refLocal.scope, + argName = arguments.DIData[ x ].argName + ); + + // some debugging goodness + if( variables.log.canDebug() ){ + variables.log.debug( "Dependency: #arguments.DIData[ x ].toString()# --> injected into #arguments.targetID#" ); + } + } + else if( variables.log.canDebug() ){ + variables.log.debug( "Dependency: #arguments.DIData[ x ].toString()# Not Found when wiring #arguments.targetID#. Registered mappings are: #structKeyList(variables.binder.getMappings())#" ); + } + } // end iteration + + return this; + } + + /** + * Inject a model object with dependencies via setters or property injections + * + * @target The target that will be injected with dependencies + * @propertyName The name of the property to inject + * @propertyObject The object to inject + * @scope The scope to inject a property into, if any else empty means it is a setter call + * @argName The name of the argument to send if setter injection + */ + private Injector function injectTarget( + required target, + required propertyName, + required propertyObject, + required scope, + required argName + ){ + var argCollection = {}; + argCollection[ arguments.argName ] = arguments.propertyObject; + // Property or Setter + if ( len( arguments.scope ) == 0 ){ + // Call our mixin invoker: setterMethod + arguments.target.invokerMixin( method="set#arguments.propertyName#", argCollection=argCollection ); + } else { + // Call our property injector mixin + arguments.target.injectPropertyMixin( + propertyName = arguments.propertyName, + propertyValue = arguments.propertyObject, + scope = arguments.scope + ); + } + + return this; + } + + /** + * Register all internal and configured WireBox Scopes + */ + private Injector function registerScopes() { + var customScopes = variables.binder.getCustomScopes(); + + // register no_scope + variables.scopes[ "NOSCOPE" ] = new wirebox.system.ioc.scopes.NoScope( this ); + // register singleton + variables.scopes[ "SINGLETON" ] = new wirebox.system.ioc.scopes.Singleton( this ); + // is cachebox linked? + if( isCacheBoxLinked() ){ + variables.scopes[ "CACHEBOX" ] = new wirebox.system.ioc.scopes.CacheBox( this ); + } + // CF Scopes and references + variables.scopes[ "REQUEST" ] = new wirebox.system.ioc.scopes.RequestScope( this ); + variables.scopes[ "SESSION" ] = new wirebox.system.ioc.scopes.CFScopes( this ); + variables.scopes[ "SERVER" ] = variables.scopes[ "SESSION" ]; + variables.scopes[ "APPLICATION" ] = variables.scopes[ "SESSION" ]; + + // Debugging + if( variables.log.canDebug() ){ + variables.log.debug( "Registered all internal lifecycle scopes successfully: #structKeyList( variables.scopes )#" ); + } + + // Register Custom Scopes + for( var key in customScopes ){ + variables.scopes[ key ] = createObject( "component", customScopes[ key ] ).init( this ); + // Debugging + if( variables.log.canDebug() ){ + variables.log.debug( "Registered custom scope: #key# (#customScopes[ key ]#)" ); + } + } + + return this; + } + + /** + * Register all the configured listeners in the configuration file + */ + private Injector function registerListeners() { + var listeners = variables.binder.getListeners(); + var regLen = arrayLen( listeners ); + + // iterate and register listeners + for( var x = 1; x lte regLen; x++ ){ + registerListener( listeners[ x ] ); + } + return this; + } + + /** + * Register all the configured listeners in the configuration file + * + * @listener The listener to register + */ + public Injector function registerListener( required listener ){ + try{ + // create it + var thisListener = createObject( "component", listener.class ); + // configure it + thisListener.configure( this, listener.properties ); + } catch( Any e ) { + variables.log.error( "Error creating listener: #listener.toString()#", e ); + throw( + message = "Error creating listener: #listener.toString()#", + detail = "#e.message# #e.detail# #e.stackTrace#", + type = "Injector.ListenerCreationException" + ); + } + + // Now register listener + if( NOT isColdBoxLinked() ){ + variables.eventManager.register( thisListener, listener.name ); + } else { + variables.eventManager.registerInterceptor( interceptorObject=thisListener, interceptorName=listener.name ); + } + + // debugging + if( variables.log.canDebug() ){ + variables.log.debug( "Injector has just registered a new listener: #listener.toString()#" ); + } + + return this; + } + + /** + * Register this injector on a user specified scope + */ + private Injector function doScopeRegistration() { + var scopeInfo = variables.binder.getScopeRegistration(); + + // register injector with scope + variables.scopeStorage.put( scopeInfo.key, this, scopeInfo.scope ); + + // Log info + if( variables.log.canDebug() ){ + variables.log.debug( "Scope Registration enabled and Injector scoped to: #scopeInfo.toString()#" ); + } + + return this; + } + + /** + * Configure a standalone version of cacheBox for persistence + * + * @config The cacheBox configuration data structure + * @config.doc_generic struct + */ + private Injector function configureCacheBox( required struct config ){ + var args = {}; + + // is cachebox enabled? + if( NOT arguments.config.enabled ){ + return this; + } + + // Do we have a cacheBox reference? + if( isObject( arguments.config.cacheFactory ) ){ + variables.cacheBox = arguments.config.cacheFactory; + // debugging + if( variables.log.canDebug() ){ + variables.log.debug( "Configured Injector #getInjectorID()# with direct CacheBox instance: #variables.cacheBox.getFactoryID()#" ); + } + return this; + } + + // Do we have a configuration file? + if( len( arguments.config.configFile ) ){ + // xml? + if( listFindNoCase( "xml,cfm", listLast( arguments.config.configFile, "." ) ) ){ + args[ "XMLConfig" ] = arguments.config.configFile; + } else { + // cfc + args[ "CFCConfigPath" ] = arguments.config.configFile; + } + + // Create CacheBox + var oConfig = createObject( "component", "#arguments.config.classNamespace#.config.CacheBoxConfig" ).init( argumentCollection=args ); + variables.cacheBox = createObject( "component", "#arguments.config.classNamespace#.CacheFactory" ).init( oConfig ); + // debugging + if( variables.log.canDebug() ){ + variables.log.debug( "Configured Injector #getInjectorID()# with CacheBox instance: #variables.cacheBox.getFactoryID()# and configuration file: #arguments.config.configFile#" ); + } + return this; + } + + // No config file, plain vanilla cachebox + variables.cacheBox = createObject( "component", "#arguments.config.classNamespace#.CacheFactory" ).init(); + // debugging + if( variables.log.canDebug() ){ + variables.log.debug( "Configured Injector #getInjectorID()# with vanilla CacheBox instance: #variables.cacheBox.getFactoryID()#" ); + } + + return this; + } + + /** + * Configure a standalone version of logBox for logging + */ + private Injector function configureLogBox( required configPath ){ + var args = structnew(); + + // xml? + if( listFindNoCase( "xml,cfm", listLast( arguments.configPath, "." ) ) ){ + args[ "XMLConfig" ] = arguments.configPath; + } else { + // cfc + args[ "CFCConfigPath" ] = arguments.configPath; + } + + var config = new wirebox.system.logging.config.LogBoxConfig( argumentCollection=args ); + + // Create LogBox + variables.logBox = new wirebox.system.logging.LogBox( config ); + // Configure Logging for this injector + variables.log = variables.logBox.getLogger( this ); + + return this; + } + + /** + * Configure a standalone version of a WireBox Event Manager + */ + private Injector function configureEventManager() { + // Use or create event manager + if( isColdBoxLinked() && isObject( variables.eventManager ) ){ + // Link Interception States + variables.eventManager.appendInterceptionPoints( variables.eventStates ); + return this; + } + + // create event manager + variables.eventManager = new wirebox.system.core.events.EventPoolManager( variables.eventStates ); + + return this; + } + + /** + * Load a configuration binder object according to passed in type + * + * @binder The data CFC configuration instance, instantiation path or programmatic binder object to configure this injector with + * @properties A map of binding properties to passthrough to the Configuration CFC + */ + private any function buildBinder( required binder, required properties ){ + // Check if just a plain CFC path and build it + if( isSimpleValue( arguments.binder ) ){ + arguments.binder = createObject( "component", arguments.binder ); + } + + // Inject Environment Support + arguments.binder[ "getSystemSetting" ] = variables.utility.getSystemSetting; + arguments.binder[ "getSystemProperty" ] = variables.utility.getSystemProperty; + arguments.binder[ "getEnv" ] = variables.utility.getEnv; + + // Check if data CFC or binder family + if( NOT isInstanceOf( arguments.binder, "wirebox.system.ioc.config.Binder" ) ){ + // simple data cfc, create native binder and decorate data CFC + var nativeBinder = new wirebox.system.ioc.config.Binder( + injector = this, + config = arguments.binder, + properties = arguments.properties + ); + } else { + // else init the binder and configure it + var nativeBinder = arguments.binder.init( injector=this, properties=arguments.properties ); + // Configure it + nativeBinder.configure(); + // Load it + nativeBinder.loadDataDSL(); + } + + return nativeBinder; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/ioc/Provider.cfc b/src/cfml/system/_wirebox/system/ioc/Provider.cfc new file mode 100644 index 000000000..d29e01e50 --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/Provider.cfc @@ -0,0 +1,108 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * A WireBox provider object that retrieves objects by using the provider pattern. + **/ +component implements="wirebox.system.ioc.IProvider" accessors="true"{ + + /** + * The name of the mapping this provider is binded to, MUTEX with name + */ + property name="name"; + + /** + * The DSL string this provider is binded to, MUTEX with name + */ + property name="dsl "; + + /** + * he injector scope registration structure + */ + property name="scopeRegistration"; + + /** + * The scope storage utiloity + */ + property name="scopeStorage"; + + /** + * The target object that requested the provider + */ + property name="targetObject"; + + /** + * Constructor + * + * @scopeRegistration The injector scope registration structure + * @scopeRegistration.doc_generic struct + * @scopeStorage The scope storage utility + * @scopeStorage.doc_generic wirebox.system.core.collections.ScopeStorage + * @name The name of the mapping this provider is binded to, MUTEX with name + * @dsl The DSL string this provider is binded to, MUTEX with name + * @targetObject The target object that requested the provider. + */ + Provider function init( + required scopeRegistration, + required scopeStorage, + name, + dsl, + required targetObject + ){ + variables.name = ""; + variables.dsl = ""; + variables.scopeRegistration = arguments.scopeRegistration; + variables.scopeStorage = arguments.scopeStorage; + variables.targetObject = arguments.targetObject; + + // Verify incoming name or DSL + if( structKeyExists( arguments, "name" ) ){ variables.name = arguments.name; } + if( structKeyExists( arguments, "dsl" ) ){ variables.dsl = arguments.dsl; } + + return this; + } + + /** + * Get the provided object + */ + any function get() { + var scopeInfo = variables.scopeRegistration; + + // Return if scope exists, else throw exception + if( variables.scopeStorage.exists( scopeInfo.key, scopeInfo.scope ) ){ + // retrieve by name or DSL + if( len( variables.name ) ){ + return variables.scopeStorage + .get( scopeInfo.key, scopeInfo.scope ) + .getInstance( name=variables.name, targetObject=variables.targetObject ); + } + + if( len( variables.dsl ) ){ + return variables.scopeStorage + .get( scopeInfo.key, scopeInfo.scope ) + .getInstance( dsl=variables.dsl, targetObject=variables.targetObject ); + } + } + + throw( + message = "Injector not found in scope registration information", + detail = "Scope information: #scopeInfo.toString()#", + type = "Provider.InjectorNotOnScope" + ); + } + + /** + * Proxy calls to provided element + * + * @missingMethodName missing method name + * @missingMethodArguments missing method arguments + */ + any function onMissingMethod( required missingMethodName, required missingMethodArguments ){ + var results = invoke( get(), arguments.missingMethodName, arguments.missingMethodArguments ); + + if ( !isNull( results ) ){ + return results; + } + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/ioc/Scopes.cfc b/src/cfml/system/_wirebox/system/ioc/Scopes.cfc new file mode 100644 index 000000000..cf5a1f906 --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/Scopes.cfc @@ -0,0 +1,45 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * A scope enum CFC that gives you the scopes that WireBox uses by default + **/ +component{ + + // DECLARED SCOPES + this.NOSCOPE = "NoScope"; + this.PROTOTYPE = "NoScope"; + this.SINGLETON = "singleton"; + this.SESSION = "session"; + this.APPLICATION = "application"; + this.REQUEST = "request"; + this.SERVER = "server"; + this.CACHEBOX = "cachebox"; + + /** + * Verify if an incoming scope is valid + * @scope The scope to check + */ + boolean function isValidScope( required scope ){ + for( var key in this ){ + if( isSimpleValue( this[ key ] ) and this[ key ] eq arguments.scope ){ + return true; + } + } + return false; + } + + /** + * Get all valid scopes as an array + */ + array function getValidScopes(){ + var scopes = {}; + for( var key in this){ + if( isSimpleValue( this[ key ] ) ){ + scopes[ key ] = this[ key ]; + } + } + return structKeyArray( scopes ); + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/ioc/Types.cfc b/src/cfml/system/_wirebox/system/ioc/Types.cfc new file mode 100644 index 000000000..2a1bdb55f --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/Types.cfc @@ -0,0 +1,19 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* A lookup static CFC that gives you the instantiation types that WireBox can talk to. +**/ +component{ + + // DECLARED WIREBOX INSTANTIATION TYPES + this.CFC = "cfc"; + this.JAVA = "java"; + this.WEBSERVICE = "webservice"; + this.RSS = "rss"; + this.DSL = "dsl"; + this.CONSTANT = "constant"; + this.FACTORY = "factory"; + this.PROVIDER = "provider"; + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/ioc/config/Binder.cfc b/src/cfml/system/_wirebox/system/ioc/config/Binder.cfc new file mode 100644 index 000000000..b04789c73 --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/config/Binder.cfc @@ -0,0 +1,1109 @@ + + + + + // Available WireBox public scopes + this.SCOPES = createObject("component","wirebox.system.ioc.Scopes"); + // Available WireBox public types + this.TYPES = createObject("component","wirebox.system.ioc.Types"); + // Utility class + this.UTILITY = createObject("component","wirebox.system.core.util.Util"); + // Contains the mappings currently being affected by the DSL. + currentMapping = []; + // Instance private scope + instance = {}; + // WireBox Defaults + DEFAULTS = { + //LogBox Defaults + logBoxConfig = "wirebox.system.ioc.config.LogBox", + // Scope Defaults + scopeRegistration = { + enabled = true, + scope = "application", + key = "wireBox" + }, + // CacheBox Integration Defaults + cacheBox = { + enabled = false, + configFile = "", + cacheFactory = "", + classNamespace = "wirebox.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 = []; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + // 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","wirebox.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]); + } + } + 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") & "/" ); + } + } + + 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()#"; + } + } + + // store the mapping info. + mapping.setScope( this.SCOPES.CACHEBOX ).setCacheProperties(argumentCollection=arguments); + + } + + 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); + } + + // Append Register Scan Locations + if( structKeyExists( wireBoxDSL, "scanLocations") ){ + scanLocations( wireBoxDSL.scanLocations ); + } + + // Append Register Stop Recursions + if( structKeyExists( wireBoxDSL, "stopRecursions") ){ + stopRecursions( wireBoxDSL.stopRecursions ); + } + + // 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(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 ){ + 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 ) { + // Remove bad mapping + instance.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","wirebox.system.aop.Matcher").init(); + + + + + + + + + + // cleanup aspect + if( isSimpleValue(arguments.aspects) ){ arguments.aspects = listToArray(arguments.aspects); } + // register it + arrayAppend(instance.aspectBindings, arguments); + + return this; + + + + + + + + + + + diff --git a/src/cfml/system/_wirebox/system/ioc/config/DefaultBinder.cfc b/src/cfml/system/_wirebox/system/ioc/config/DefaultBinder.cfc new file mode 100644 index 000000000..1f4fa0ab0 --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/config/DefaultBinder.cfc @@ -0,0 +1,66 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* The default ColdBox WireBox Injector configuration object that is used when the +* WireBox injector is created +**/ +component extends="wirebox.system.ioc.config.Binder"{ + + /** + * Configure WireBox, that's it! + */ + function configure(){ + + // The WireBox configuration structure DSL + wireBox = { + // Default LogBox Configuration file + logBoxConfig = "wirebox.system.ioc.config.LogBox", + + // CacheBox Integration OFF by default + cacheBox = { + enabled = false + // configFile = "wirebox.system.ioc.config.CacheBox", An optional configuration file to use for loading CacheBox + // cacheFactory = "" A reference to an already instantiated CacheBox CacheFactory + // classNamespace = "" A class path namespace to use to create CacheBox: Default=wirebox.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" + }, + + // 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={} } + ] + }; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/ioc/config/LogBox.cfc b/src/cfml/system/_wirebox/system/ioc/config/LogBox.cfc new file mode 100644 index 000000000..f5007f9bc --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/config/LogBox.cfc @@ -0,0 +1,33 @@ +/** +* 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{ + + /** + * Configure logBox + */ + function configure(){ + logBox = { + // Define Appenders + appenders = { + console = { + class="wirebox.system.logging.appenders.ConsoleAppender" + } + /**, + cflogs = { + class="wirebox.system.logging.appenders.CFAppender", + properties = { fileName="ColdBox-WireBox"} + } + **/ + }, + // Root Logger + root = { levelmax="INFO", appenders="*" } + }; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/ioc/config/Mapping.cfc b/src/cfml/system/_wirebox/system/ioc/config/Mapping.cfc new file mode 100644 index 000000000..457905232 --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/config/Mapping.cfc @@ -0,0 +1,947 @@ + + + + + + + + + + + // 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 ); + } + + // 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; + } + + // 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 DISetters + case "DISetters" : { + for( x=1; x lte arrayLen( arguments.memento.DISetters ); x++){ + addDISetter( argumentCollection=arguments.memento.DISetters[ x ] ); + } + break; + } + + // process DIMethodArgs + case "DIMethodArgs" : { + for( x=1; x lte arrayLen( arguments.memento.DIMethodArgs ); x++){ + addDIMethodArgument( argumentCollection=arguments.memento.DIMethodArgs[ 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; + } + + default:{ + instance[ key ] = arguments.memento[ key ]; + break; + } + }// end switch + + } + + 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;} + } + // 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;} + } + 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;} + } + 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;} + } + // 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; + } + // save incoming params + structAppend(def, arguments, true); + // save new DI setter injection + arrayAppend( instance.DISetters, def ); + return this; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + if( NOT instance.discovered ){ + // announce inspection + iData = {mapping=this,binder=arguments.binder,injector=arguments.binder.getInjector()}; + eventManager.processState("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.processState("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; + + // 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; + } + + // check if scope found? If so, then set it to no scope. + if( NOT len(instance.scope) ){ instance.scope = "noscope"; } + + } // end of persistence checks + + // 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; + } + } + } + + // 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; + } + } + + // eagerInit annotation + if( NOT len(instance.eagerInit) ){ + if( structKeyExists(md,"eagerInit") ){ + instance.eagerInit = true; + } + else{ + // defaults to lazy loading + instance.eagerInit = false; + } + } + + // threadSafe wiring annotation + if( NOT len(instance.threadSafe) ){ + if( structKeyExists(md,"threadSafe") AND NOT len(md.threadSafe)){ + instance.threadSafe = true; + } + else if( structKeyExists(md,"threadSafe") AND len(md.threadSafe) AND isBoolean(md.threadSafe) ){ + instance.threadSafe = md.threadSafe; + } + else{ + // defaults to non thread safe wiring + instance.threadSafe = false; + } + } + + // mixins annotation only if not overriden + if( NOT arrayLen(instance.mixins) ){ + if( structKeyExists(md,"mixins") ){ + instance.mixins = listToArray( md.mixins ); + } + } + + // 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; + } + } + + // 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); + } + + // 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 ); + } + + // 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 + instance.discovered = true; + + // announce it + eventManager.processState("afterInstanceInspection",iData); + } + + + + + + + + + + + + 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; + } + 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; + } + 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()); + + + + + + + + + + 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; + } + // Add to property to mappings + addDIProperty( name=params.name, dsl=params.inject, scope=params.scope, required=params.required, type=params.type ); + } + + } + }//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 + + + + + + + + + + \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/ioc/config/Mixin.cfc b/src/cfml/system/_wirebox/system/ioc/config/Mixin.cfc new file mode 100644 index 000000000..1651756dd --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/config/Mixin.cfc @@ -0,0 +1,25 @@ +component{ + + function $init( required mixins ){ + + // Include the mixins + for( var thisMixin in arguments.mixins ){ + thisMixin = trim( thisMixin ); + if( listLast( thisMixin, "." ) != "cfm" ){ + include "#thisMixin#.cfm"; + } else { + include "#thisMixin#"; + } + } + + // Expose them + 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/src/cfml/system/_wirebox/system/ioc/dsl/CacheBoxDSL.cfc b/src/cfml/system/_wirebox/system/ioc/dsl/CacheBoxDSL.cfc new file mode 100644 index 000000000..fbc165a98 --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/dsl/CacheBoxDSL.cfc @@ -0,0 +1,88 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* Process DSL functions via CacheBox +**/ +component implements="wirebox.system.ioc.dsl.IDSLBuilder" accessors="true"{ + + /** + * Injector Reference + */ + property name="injector"; + + /** + * CacheBox Reference + */ + property name="cacheBox"; + + /** + * Log Reference + */ + property name="log"; + + /** + * Configure the DSL Builder for operation and returns itself + * + * @injector The linked WireBox Injector + * @injector.doc_generic wirebox.system.ioc.Injector + * + * @return wirebox.system.ioc.dsl.IDSLBuilder + */ + function init( required injector ){ + variables.injector = arguments.injector; + variables.cacheBox = variables.injector.getCacheBox(); + variables.log = variables.injector.getLogBox().getLogger( this ); + + return this; + } + + /** + * Process an incoming DSL definition and produce an object with it + * + * @definition The injection dsl definition structure to process. Keys: name, dsl + * @targetObject The target object we are building the DSL dependency for. If empty, means we are just requesting building + * + * @return wirebox.system.ioc.dsl.IDSLBuilder + */ + function process( required definition, targetObject ){ + var thisType = arguments.definition.dsl; + var thisTypeLen = listLen( thisType, ":" ); + + // DSL stages + switch( thisTypeLen ){ + // CacheBox + case 1 : { + return variables.cacheBox; + } + + // CacheBox:CacheName + case 2 : { + var cacheName = getToken( thisType, 2, ":" ); + // Verify that cache exists + if( variables.cacheBox.cacheExists( cacheName ) ){ + return variables.cacheBox.getCache( cacheName ); + } + else if( variables.log.canDebug() ){ + variables.log.debug( "getCacheBoxDSL() cannot find named cache #cacheName# using definition: #arguments.definition.toString()#. Existing cache names are #variables.cacheBox.getCacheNames().toString()#" ); + } + break; + } + + // CacheBox:CacheName:Element + case 3 : { + var cacheName = getToken( thisType, 2, ":" ); + var cacheElement = getToken( thisType, 3, ":" ); + // Verify that dependency exists in the Cache container + if( variables.cacheBox.getCache( cacheName ).lookup( cacheElement ) ){ + return variables.cacheBox.getCache( cacheName ).get( cacheElement ); + } + else if( variables.log.canDebug() ){ + variables.log.debug( "getCacheBoxDSL() cannot find cache Key: #cacheElement# in the #cacheName# cache using definition: #arguments.definition.toString()#" ); + } + break; + } // end level 3 main DSL + } + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/ioc/dsl/ColdBoxDSL.cfc b/src/cfml/system/_wirebox/system/ioc/dsl/ColdBoxDSL.cfc new file mode 100644 index 000000000..9614a6861 --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/dsl/ColdBoxDSL.cfc @@ -0,0 +1,174 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* Process DSL functions via ColdBox +**/ +component implements="wirebox.system.ioc.dsl.IDSLBuilder" accessors="true"{ + + /** + * Injector Reference + */ + property name="injector"; + + /** + * CacheBox Reference + */ + property name="cachebox"; + + /** + * ColdBox Reference + */ + property name="coldbox"; + + /** + * Log Reference + */ + property name="log"; + + /** + * Configure the DSL Builder for operation and returns itself + * + * @injector The linked WireBox Injector + * @injector.doc_generic wirebox.system.ioc.Injector + * + * @return wirebox.system.ioc.dsl.IDSLBuilder + */ + function init( required injector ){ + variables.injector = arguments.injector; + variables.coldbox = variables.injector.getColdBox(); + variables.cacheBox = variables.injector.getCacheBox(); + variables.log = variables.injector.getLogBox().getLogger( this ); + + return this; + } + + /** + * Process an incoming DSL definition and produce an object with it + * + * @definition The injection dsl definition structure to process. Keys: name, dsl + * @targetObject The target object we are building the DSL dependency for. If empty, means we are just requesting building + * + * @return wirebox.system.ioc.dsl.IDSLBuilder + */ + function process( required definition, targetObject ){ + var DSLNamespace = listFirst( arguments.definition.dsl, ":" ); + + switch( DSLNamespace ){ + case "coldbox" : { return getColdboxDSL( argumentCollection=arguments ); } + } + + // else ignore not our DSL + } + + /******************************** PRIVATE ****************************************************************/ + + /** + * Process a ColdBox DSL + * + * @definition The injection dsl definition structure to process. Keys: name, dsl + * @targetObject The target object we are building the DSL dependency for. If empty, means we are just requesting building + */ + private function getColdBoxDSL( required definition, targetObject ){ + var thisName = arguments.definition.name; + var thisType = arguments.definition.dsl; + var thisTypeLen = listLen( thisType, ":" ); + var thisLocationType = ""; + var thisLocationKey = ""; + var moduleSettings = ""; + + // Support shortcut for specifying name in the definition instead of the DSl for supporting namespaces + if( thisTypeLen eq 2 + and listFindNoCase( "setting,fwSetting,datasource,interceptor", listLast( thisType, ":" ) ) + and len( thisName ) + ){ + // Add the additional alias to the DSL + thisType = thisType & ":" & thisName; + thisTypeLen = 3; + } + + // DSL stages + switch( thisTypeLen ){ + // coldbox only DSL + case 1 : { + return variables.coldbox; + } + + // coldbox:{key} stage 2 + case 2 : { + thisLocationKey = getToken( thisType, 2, ":" ); + switch( thisLocationKey ){ + case "configSettings" : { return variables.coldbox.getConfigSettings(); } + case "dataMarshaller" : { return variables.coldbox.getDataMarshaller(); } + case "flash" : { return variables.coldbox.getRequestService().getFlashScope(); } + case "fwSettings" : { return variables.coldbox.getColdboxSettings(); } + case "handlerService" : { return variables.coldbox.getHandlerService(); } + case "interceptorService" : { return variables.coldbox.getInterceptorService(); } + case "loaderService" : { return variables.coldbox.getLoaderService(); } + case "moduleService" : { return variables.coldbox.getModuleService(); } + case "requestContext" : { return variables.coldbox.getRequestService().getContext(); } + case "requestService" : { return variables.coldbox.getRequestService(); } + case "router" : { return variables.injector.getInstance( "router@coldbox" ); } + case "routingService" : { return variables.coldbox.getRoutingService(); } + case "renderer" : { return variables.coldbox.getRenderer(); } + } // end of services + + break; + } + + //coldbox:{key}:{target} Usually for named factories + case 3 : { + thisLocationType = getToken( thisType, 2, ":" ); + thisLocationKey = getToken( thisType, 3, ":" ); + switch( thisLocationType ){ + case "setting" : { + // module setting? + if( find( "@", thisLocationKey ) ){ + moduleSettings = variables.coldbox.getSetting( "modules" ); + if( structKeyExists( moduleSettings, listlast(thisLocationKey, "@" )) + and structKeyExists( moduleSettings[ listlast(thisLocationKey, "@" ) ],"settings" ) + and structKeyExists( moduleSettings[ listlast(thisLocationKey, "@" ) ].settings,listFirst( thisLocationKey, "@" ) ) + ){ + return moduleSettings[ listlast(thisLocationKey, "@" ) ].settings[ listFirst( thisLocationKey, "@" ) ]; + } + else if( variables.log.canDebug() ){ + variables.log.debug( "The module requested: #listlast( thisLocationKey, "@" )# does not exist in the loaded modules. Loaded modules are #structKeyList(moduleSettings)#" ); + } + } + // just get setting + return variables.coldbox.getSetting( thisLocationKey ); + } + case "modulesettings" : { + moduleSettings = variables.coldbox.getSetting( "modules" ); + if( structKeyExists( moduleSettings, thisLocationKey ) ){ + return moduleSettings[ thisLocationKey ].settings; + } + else if( variables.log.canDebug() ){ + variables.log.debug( "The module requested: #thisLocationKey# does not exist in the loaded modules. Loaded modules are #structKeyList(moduleSettings)#" ); + } + } + case "moduleconfig" : { + moduleSettings = variables.coldbox.getSetting( "modules" ); + if( structKeyExists( moduleSettings, thisLocationKey ) ){ + return moduleSettings[ thisLocationKey ]; + } + else if( variables.log.canDebug() ){ + variables.log.debug( "The module requested: #thisLocationKey# does not exist in the loaded modules. Loaded modules are #structKeyList(moduleSettings)#" ); + } + } + case "fwSetting" : { return variables.coldbox.getSetting( thisLocationKey, true ); } + case "interceptor" : { return variables.coldbox.getInterceptorService().getInterceptor( thisLocationKey, true ); } + }//end of services + + break; + } + } + + // If we get here we have a problem. + throw( + type = "ColdBoxDSL.InvalidDSL", + message = "The DSL provided was not valid: #arguments.definition.toString()#" + ); + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/ioc/dsl/IDSLBuilder.cfc b/src/cfml/system/_wirebox/system/ioc/dsl/IDSLBuilder.cfc new file mode 100644 index 000000000..66d0f0d2f --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/dsl/IDSLBuilder.cfc @@ -0,0 +1,29 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* The main interface to produce WireBox namespace DSL Builders +**/ +interface{ + + /** + * Configure the DSL Builder for operation and returns itself + * + * @injector The linked WireBox Injector + * @injector.doc_generic wirebox.system.ioc.Injector + * + * @return wirebox.system.ioc.dsl.IDSLBuilder + */ + function init( required injector ); + + /** + * Process an incoming DSL definition and produce an object with it + * + * @definition The injection dsl definition structure to process. Keys: name, dsl + * @targetObject The target object we are building the DSL dependency for. If empty, means we are just requesting building + * + * @return wirebox.system.ioc.dsl.IDSLBuilder + */ + function process( required definition, targetObject ); + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/ioc/dsl/LogBoxDSL.cfc b/src/cfml/system/_wirebox/system/ioc/dsl/LogBoxDSL.cfc new file mode 100644 index 000000000..8fc3931ba --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/dsl/LogBoxDSL.cfc @@ -0,0 +1,90 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* Process DSL functions via LogBox +**/ +component implements="wirebox.system.ioc.dsl.IDSLBuilder" accessors="true"{ + + /** + * Injector Reference + */ + property name="injector"; + + /** + * LogBox Reference + */ + property name="logBox"; + + /** + * Log Reference + */ + property name="log"; + + /** + * Configure the DSL Builder for operation and returns itself + * + * @injector The linked WireBox Injector + * @injector.doc_generic wirebox.system.ioc.Injector + * + * @return wirebox.system.ioc.dsl.IDSLBuilder + */ + function init( required injector ){ + variables.injector = arguments.injector; + variables.logBox = variables.injector.getLogBox(); + variables.log = variables.injector.getLogBox().getLogger( this ); + + return this; + } + + /** + * Process an incoming DSL definition and produce an object with it + * + * @definition The injection dsl definition structure to process. Keys: name, dsl + * @targetObject The target object we are building the DSL dependency for. If empty, means we are just requesting building + * + * @return wirebox.system.ioc.dsl.IDSLBuilder + */ + function process( required definition, targetObject ){ + var thisType = arguments.definition.dsl; + var thisTypeLen = listLen( thisType, ":" ); + + // DSL stages + switch( thisTypeLen ){ + // logbox + case 1 : { + return variables.logBox; + } + + // logbox:root and logbox:logger + case 2 : { + var thisLocationKey = getToken( thisType, 2, ":" ); + switch( thisLocationKey ){ + case "root" : { return variables.logbox.getRootLogger(); } + case "logger" : { return variables.logbox.getLogger( arguments.definition.name ); } + } + break; + } + + // Named Loggers + case 3 : { + var thisLocationType = getToken( thisType, 2, ":" ); + var thisLocationKey = getToken( thisType, 3, ":" ); + // DSL Level 2 Stage Types + switch( thisLocationType ){ + // Get a named Logger + case "logger" : { + // Check for {this} and targetobject exists + if( thisLocationKey eq "{this}" AND structKeyExists( arguments, "targetObject" ) ){ + return variables.logBox.getLogger( arguments.targetObject ); + } + // Normal Logger injection + return variables.logBox.getLogger( thisLocationKey ); break; + } + } + break; + } // end level 3 main DSL + } + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/ioc/license.txt b/src/cfml/system/_wirebox/system/ioc/license.txt new file mode 100644 index 000000000..3de5291cb --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/license.txt @@ -0,0 +1,22 @@ +******************************************************************************** +Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +www.ortussolutions.com +******************************************************************************** +ColdBox is open source. However, if you use this product please know that it is bound to the following Licence. +If you use ColdBox, please make mention of it in your code or web site or add a Powered By Coldbox icon. + +Apache License, Version 2.0 + +Copyright [2007] [Luis Majano and Ortus Solutions,Corp] + +Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/ioc/readme.md b/src/cfml/system/_wirebox/system/ioc/readme.md new file mode 100644 index 000000000..888303c10 --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/readme.md @@ -0,0 +1,95 @@ +``` + ___ ___ _____ ______ _____ ______ ____ __ __ +( ( ) ) (_ _) ( __ \ / ___/ (_ _ \ / __ \ (_ \ / _) + \ \ _ / / | | ) (__) ) ( (__ ) (_) ) / / \ \ \ \_/ / + \ \/ \/ / | | ( __/ ) __) \ _/ ( () () ) \ / + ) _ ( | | ) \ \ _ ( ( / _ \ ( () () ) / _ \ + \ ( ) / _| |__ ( ( \ \_)) \ \___ _) (_) ) \ \__/ / _/ / \ \_ + \_/ \_/ /_____( )_) \__/ \____\ (______/ \____/ (__/ \__) + +``` + +Copyright Since 2005 ColdBox Platform by Luis Majano and Ortus Solutions, Corp +www.coldbox.org | www.ortussolutions.com + +---- + +# Welcome to WireBox +WireBox is a conventions based ColdFusion (CFML) dependency injection and AOP framework. + +## License +Apache License, Version 2.0. + +>The ColdBox Websites, logo and content have a separate license and they are a separate entity. + +## Versioning +WireBox is maintained under the Semantic Versioning guidelines as much as possible. + +Releases will be numbered with the following format: + +``` +.. +``` + +And constructed with the following guidelines: + +* Breaking backward compatibility bumps the major (and resets the minor and patch) +* New additions without breaking backward compatibility bumps the minor (and resets the patch) +* Bug fixes and misc changes bumps the patch + +## Important Links + +Source Code +- https://github.com/coldbox/coldbox-platform + +Continuous Integration +- https://travis-ci.org/ColdBox/coldbox-platform + +Bug Tracking/Agile Boards +- https://ortussolutions.atlassian.net/browse/WIREBOX + +Documentation +- https://wirebox.ortusbooks.com + +Blog +- https://www.ortussolutions.com/blog + +Official Site +- https://www.coldbox.org + +## System Requirements +- Lucee 4.5+ +- ColdFusion 11+ + +## Quick Installation +Please go to our [documentation](https://wirebox.ortusbooks.com) for expanded instructions. + +**CommandBox (Recommended)** + +We recommend you use [CommandBox](https://www.ortussolutions.com/products/commandbox), our CFML CLI and package manager, to install WireBox. + +**Stable Release** + +`box install wirebox` + +**Bleeding Edge Release** + +`box install wirebox-be` + +**Simple Install** + +Unzip the download into a folder called `wirebox` in your webroot or place outside of the webroot and create a per-application mapping `/wirebox` that points to it. + +**Bleeding Edge Downloads** +You can always leverage our bleeding edge artifacts server to download WireBox: http://downloads.ortussolutions.com/#/ortussolutions/wirebox/ + +--- + +Because of God's grace, this project exists. If you don't like this, then don't read it, its not for you. + +>"Therefore being justified by faith, we have peace with God through our Lord Jesus Christ: +By whom also we have access by faith into this grace wherein we stand, and rejoice in hope of the glory of God. +And not only so, but we glory in tribulations also: knowing that tribulation worketh patience; +And patience, experience; and experience, hope: +And hope maketh not ashamed; because the love of God is shed abroad in our hearts by the +Holy Ghost which is given unto us. ." Romans 5:5 \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/ioc/scopes/CFScopes.cfc b/src/cfml/system/_wirebox/system/ioc/scopes/CFScopes.cfc new file mode 100644 index 000000000..2b0b2590d --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/scopes/CFScopes.cfc @@ -0,0 +1,114 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* A scope that stores in valid CF scopes +**/ +component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ + + /** + * Injector linkage + */ + property name="injector"; + + /** + * Scope Storage Reference + */ + property name="scopeStorage"; + + /** + * Log Reference + */ + property name="log"; + + /** + * Configure the scope for operation and returns itself + * + * + * @injector The linked WireBox injector + * @injector.doc_generic wirebox.system.ioc.Injector + * + * @return wirebox.system.ioc.scopes.IScope + */ + function init( required injector ){ + variables.injector = arguments.injector; + variables.scopeStorage = new wirebox.system.core.collections.ScopeStorage(); + variables.log = arguments.injector.getLogBox().getLogger( this ); + return this; + } + + /** + * Retrieve an object from scope or create it if not found in scope + * + * + * @mapping The linked WireBox injector + * @mapping.doc_generic wirebox.system.ioc.config.Mapping + * @initArguments The constructor struct of arguments to passthrough to initialization + * @initArguments.doc_generic struct + */ + function getFromScope( required mapping, initArguments ){ + var CFScope = arguments.mapping.getScope(); + var cacheKey = "wirebox:#arguments.mapping.getName()#"; + + // Verify it + if( !variables.scopeStorage.exists( cacheKey, CFScope ) ){ + // Lock it + lock name="WireBox.#variables.injector.getInjectorID()#.#CFScope#.#cacheKey#" + type="exclusive" + timeout="30" + throwontimeout="true"{ + + if( !variables.scopeStorage.exists( cacheKey, CFScope ) ){ + + // some nice debug info. + if( variables.log.canDebug() ){ + variables.log.debug( "Object: (#arguments.mapping.getName()#) not found in CFScope (#CFScope#), beggining construction." ); + } + + // construct the variables + var target = variables.injector.buildInstance( arguments.mapping, arguments.initArguments ); + + // If not in wiring thread safety, store in scope to satisfy circular dependencies + if( NOT arguments.mapping.getThreadSafe() ){ + variables.scopeStorage.put( cacheKey, target, CFScope ); + } + + // wire it + variables.injector.autowire( target=target, mapping=arguments.mapping ); + + // If thread safe, then now store it in the scope, as all dependencies are now safely wired + if( arguments.mapping.getThreadSafe() ){ + variables.scopeStorage.put( cacheKey, target, CFScope ); + } + + // log it + if( variables.log.canDebug() ){ + variables.log.debug( "Object: (#arguments.mapping.getName()#) constructed and stored in CFScope (#CFScope#), threadSafe=#arguments.mapping.getThreadSafe()#." ); + } + + return target; + } + + }// end lock + } + + return variables.scopeStorage.get( cacheKey, CFScope ); + } + + + /** + * Indicates whether an object exists in scope + * + * @mapping The linked WireBox injector + * @mapping.doc_generic wirebox.system.ioc.config.Mapping + * + * @return wirebox.system.ioc.scopes.IScope + */ + boolean function exists( required mapping ){ + var cacheKey = "wirebox:#arguments.mapping.getName()#"; + var CFScope = arguments.mapping.getScope(); + + return variables.scopeStorage.exists( cacheKey, CFScope ); + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/ioc/scopes/CacheBox.cfc b/src/cfml/system/_wirebox/system/ioc/scopes/CacheBox.cfc new file mode 100644 index 000000000..a4e3a59e3 --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/scopes/CacheBox.cfc @@ -0,0 +1,130 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* A scope that interfaces with CacheBox +**/ +component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ + + /** + * Injector linkage + */ + property name="injector"; + + /** + * CacheBox Reference + */ + property name="cacheBox"; + + /** + * Log Reference + */ + property name="log"; + + /** + * Configure the scope for operation and returns itself + * + * + * @injector The linked WireBox injector + * @injector.doc_generic wirebox.system.ioc.Injector + * + * @return wirebox.system.ioc.scopes.IScope + */ + function init( required injector ){ + variables.injector = arguments.injector; + variables.cacheBox = arguments.injector.getCacheBox(); + variables.log = arguments.injector.getLogBox().getLogger( this ); + return this; + } + + /** + * Retrieve an object from scope or create it if not found in scope + * + * + * @mapping The linked WireBox injector + * @mapping.doc_generic wirebox.system.ioc.config.Mapping + * @initArguments The constructor struct of arguments to passthrough to initialization + * @initArguments.doc_generic struct + */ + function getFromScope( required mapping, initArguments ){ + var cacheProperties = arguments.mapping.getCacheProperties(); + var refLocal = {}; + var cacheProvider = variables.cacheBox.getCache( cacheProperties.provider ); + var cacheKey = "#cacheProperties.key#"; + + // Get From Cache + refLocal.target = cacheProvider.get( cacheKey ); + + // Verify it + if( isNull( refLocal.target ) ){ + // Lock it + lock name="WireBox.#variables.injector.getInjectorID()#.CacheBoxScope.#arguments.mapping.getName()#" + type="exclusive" + timeout="30" + throwontimeout="true"{ + + // Double get just in case of race conditions + refLocal.target = cacheProvider.get( cacheKey ); + if( !isNull( refLocal.target ) ){ + return refLocal.target; + } + + // some nice debug info. + if( variables.log.canDebug() ){ + variables.log.debug("Object: (#cacheProperties.toString()#) not found in cacheBox, beginning construction."); + } + + // construct it + refLocal.target = variables.injector.buildInstance( arguments.mapping, arguments.initArguments ); + + // If not in wiring thread safety, store in singleton cache to satisfy circular dependencies + if( NOT arguments.mapping.getThreadSafe() ){ + cacheProvider.set( + cacheKey, + refLocal.target, + cacheProperties.timeout, + cacheProperties.lastAccessTimeout + ); + } + + // wire up dependencies on the object + variables.injector.autowire( target=refLocal.target, mapping=arguments.mapping ); + + // If thread safe, then now store it in the cache, as all dependencies are now safely wired + if( arguments.mapping.getThreadSafe() ){ + cacheProvider.set( + cacheKey, + refLocal.target, + cacheProperties.timeout, + cacheProperties.lastAccessTimeout + ); + } + + // log it + if( variables.log.canDebug() ){ + variables.log.debug( "Object: (#cacheProperties.toString()#) constructed and stored in cacheBox. ThreadSafe=#arguments.mapping.getThreadSafe()#" ); + } + + // return it + return refLocal.target; + + }// end lock + } else { + return refLocal.target; + } + } + + + /** + * Indicates whether an object exists in scope + * + * @mapping The linked WireBox injector + * @mapping.doc_generic wirebox.system.ioc.config.Mapping + * + * @return wirebox.system.ioc.scopes.IScope + */ + boolean function exists( required mapping ){ + return variables.cacheProvider.lookupQuiet( arguments.mapping.getCacheProperties().key ); + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/ioc/scopes/IScope.cfc b/src/cfml/system/_wirebox/system/ioc/scopes/IScope.cfc new file mode 100644 index 000000000..d2881d408 --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/scopes/IScope.cfc @@ -0,0 +1,42 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* The main interface to produce WireBox storage scopes +**/ +interface{ + + /** + * Configure the scope for operation and returns itself + * + * + * @injector The linked WireBox injector + * @injector.doc_generic wirebox.system.ioc.Injector + * + * @return wirebox.system.ioc.scopes.IScope + */ + function init( required injector ); + + /** + * Retrieve an object from scope or create it if not found in scope + * + * + * @mapping The linked WireBox injector + * @mapping.doc_generic wirebox.system.ioc.config.Mapping + * @initArguments The constructor struct of arguments to passthrough to initialization + * @initArguments.doc_generic struct + */ + function getFromScope( required mapping, initArguments ); + + + /** + * Indicates whether an object exists in scope + * + * @mapping The linked WireBox injector + * @mapping.doc_generic wirebox.system.ioc.config.Mapping + * + * @return wirebox.system.ioc.scopes.IScope + */ + boolean function exists( required mapping ); + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/ioc/scopes/NoScope.cfc b/src/cfml/system/_wirebox/system/ioc/scopes/NoScope.cfc new file mode 100644 index 000000000..8c8ac6cee --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/scopes/NoScope.cfc @@ -0,0 +1,65 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* A no scope scope scope :) +**/ +component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ + + /** + * Injector linkage + */ + property name="injector"; + + /** + * Log Reference + */ + property name="log"; + + /** + * Configure the scope for operation and returns itself + * + * + * @injector The linked WireBox injector + * @injector.doc_generic wirebox.system.ioc.Injector + * + * @return wirebox.system.ioc.scopes.IScope + */ + function init( required injector ){ + variables.injector = arguments.injector; + variables.log = arguments.injector.getLogBox().getLogger( this ); + return this; + } + + /** + * Retrieve an object from scope or create it if not found in scope + * + * + * @mapping The linked WireBox injector + * @mapping.doc_generic wirebox.system.ioc.config.Mapping + * @initArguments The constructor struct of arguments to passthrough to initialization + * @initArguments.doc_generic struct + */ + function getFromScope( required mapping, initArguments ){ + // create and return the no scope instance, no locking needed. + var object = variables.injector.buildInstance( arguments.mapping, arguments.initArguments ); + // wire it + variables.injector.autowire( target=object, mapping=arguments.mapping ); + // send it back + return object; + } + + + /** + * Indicates whether an object exists in scope + * + * @mapping The linked WireBox injector + * @mapping.doc_generic wirebox.system.ioc.config.Mapping + * + * @return wirebox.system.ioc.scopes.IScope + */ + boolean function exists( required mapping ){ + return false; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/ioc/scopes/RequestScope.cfc b/src/cfml/system/_wirebox/system/ioc/scopes/RequestScope.cfc new file mode 100644 index 000000000..d57d870a6 --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/scopes/RequestScope.cfc @@ -0,0 +1,85 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* A scope that leverages the request scope +**/ +component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ + + /** + * Injector linkage + */ + property name="injector"; + + /** + * Log Reference + */ + property name="log"; + + /** + * Configure the scope for operation and returns itself + * + * + * @injector The linked WireBox injector + * @injector.doc_generic wirebox.system.ioc.Injector + * + * @return wirebox.system.ioc.scopes.IScope + */ + function init( required injector ){ + variables.injector = arguments.injector; + variables.log = arguments.injector.getLogBox().getLogger( this ); + return this; + } + + /** + * Retrieve an object from scope or create it if not found in scope + * + * + * @mapping The linked WireBox injector + * @mapping.doc_generic wirebox.system.ioc.config.Mapping + * @initArguments The constructor struct of arguments to passthrough to initialization + * @initArguments.doc_generic struct + */ + function getFromScope( required mapping, initArguments ){ + var cacheKey = "wirebox:#arguments.mapping.getName()#"; + + // Check if already in request scope + if( NOT structKeyExists( request, cacheKey ) ){ + // some nice debug info. + if( variables.log.canDebug() ){ + variables.log.debug( "Object: (#arguments.mapping.getName()#) not found in request scope, beggining construction." ); + } + + // construct it and store it, to satisfy circular dependencies + var target = variables.injector.buildInstance( arguments.mapping, arguments.initArguments ); + request[ cacheKey ] = target; + + // wire it + variables.injector.autowire( target=target, mapping=arguments.mapping ); + + // log it + if( variables.log.canDebug() ){ + variables.log.debug( "Object: (#arguments.mapping.getName()#) constructed and stored in Request scope." ); + } + + return target; + } + + return request[ cacheKey ]; + } + + + /** + * Indicates whether an object exists in scope + * + * @mapping The linked WireBox injector + * @mapping.doc_generic wirebox.system.ioc.config.Mapping + * + * @return wirebox.system.ioc.scopes.IScope + */ + boolean function exists( required mapping ){ + var cacheKey = "wirebox:#arguments.mapping.getName()#"; + return structKeyExists( request, cacheKey ); + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/ioc/scopes/Singleton.cfc b/src/cfml/system/_wirebox/system/ioc/scopes/Singleton.cfc new file mode 100644 index 000000000..bc7696a1e --- /dev/null +++ b/src/cfml/system/_wirebox/system/ioc/scopes/Singleton.cfc @@ -0,0 +1,121 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* Tracking of single instance objects: Singletons +**/ +component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ + + /** + * Injector linkage + */ + property name="injector"; + + /** + * Track singletons as a concurrent hash map + */ + property name="singletons"; + + /** + * Log Reference + */ + property name="log"; + + /** + * Configure the scope for operation and returns itself + * + * + * @injector The linked WireBox injector + * @injector.doc_generic wirebox.system.ioc.Injector + * + * @return wirebox.system.ioc.scopes.IScope + */ + function init( required injector ){ + variables.injector = arguments.injector; + variables.singletons = createObject( "java", "java.util.concurrent.ConcurrentHashMap" ).init(); + variables.log = arguments.injector.getLogBox().getLogger( this ); + return this; + } + + /** + * Retrieve an object from scope or create it if not found in scope + * + * + * @mapping The linked WireBox injector + * @mapping.doc_generic wirebox.system.ioc.config.Mapping + * @initArguments The constructor struct of arguments to passthrough to initialization + * @initArguments.doc_generic struct + */ + function getFromScope( required mapping, initArguments ){ + var cacheKey = lcase( arguments.mapping.getName() ); + + // Verify in Singleton Cache + if( NOT structKeyExists( variables.singletons, cacheKey ) ){ + + // Lock it + lock name="WireBox.#variables.injector.getInjectorID()#.Singleton.#cacheKey#" + type="exclusive" + timeout="30" + throwontimeout="true"{ + + // double lock it + if( NOT structKeyExists( variables.singletons, cacheKey) ){ + + // some nice debug info. + if( variables.log.canDebug() ){ + variables.log.debug("Object: (#cacheKey#) not found in singleton cache, beggining construction."); + } + + // construct the singleton object + var tmpSingleton = variables.injector.buildInstance( arguments.mapping, arguments.initArguments); + + // If not in wiring thread safety, store in singleton cache to satisfy circular dependencies + if( NOT arguments.mapping.getThreadSafe() ){ + variables.singletons[ cacheKey ] = tmpSingleton; + } + + // wire up dependencies on the singleton object + variables.injector.autowire( target=tmpSingleton, mapping=arguments.mapping ); + + // If thread safe, then now store it in the singleton cache, as all dependencies are now safely wired + if( arguments.mapping.getThreadSafe() ){ + variables.singletons[ cacheKey ] = tmpSingleton; + } + + // log it + if( variables.log.canDebug() ){ + variables.log.debug( "Object: (#cacheKey#) constructed and stored in singleton cache. ThreadSafe=#arguments.mapping.getThreadSafe()#" ); + } + + // return it + return variables.singletons[ cacheKey ]; + } + + } // end lock + } + + // return singleton + return variables.singletons[ cacheKey ]; + } + + /** + * Indicates whether an object exists in scope + * + * @mapping The linked WireBox injector + * @mapping.doc_generic wirebox.system.ioc.config.Mapping + * + * @return wirebox.system.ioc.scopes.IScope + */ + boolean function exists( required mapping ){ + return structKeyExists( variables.singletons, lcase( arguments.mapping.getName() ) ); + } + + /** + * Clear the singletons scopes + */ + function clear(){ + variables.singletons = createObject( "java", "java.util.concurrent.ConcurrentHashMap" ).init(); + return this; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/logging/AbstractAppender.cfc b/src/cfml/system/_wirebox/system/logging/AbstractAppender.cfc new file mode 100644 index 000000000..d73996985 --- /dev/null +++ b/src/cfml/system/_wirebox/system/logging/AbstractAppender.cfc @@ -0,0 +1,248 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* This component is used as a base for creating LogBox appenders +**/ +component accessors="true"{ + + /** + * Min logging level + */ + property name="levelMin" type="numeric"; + + /** + * Max logging level + */ + property name="levelMax" type="numeric"; + + /** + * Appender properties + */ + property name="properties" type="struct"; + + /** + * Appender name + */ + property name="name" default=""; + + /** + * Appender initialized flag + */ + property name="initialized" type="boolean" default="false"; + + /** + * Appender customLayout for rendering messages + */ + property name="customLayout"; + + /** + * ColdBox Controller Linkage, empty if in standalone mode. + */ + property name="coldbox"; + + // The log levels enum as a public property + this.logLevels = new wirebox.system.logging.LogLevels(); + + /** + * Constructor + * + * @name The unique name for this appender. + * @properties A map of configuration properties for the appender" + * @layout The layout class to use in this appender for custom message rendering. + * @levelMin The default log level for this appender, by default it is 0. Optional. ex: LogBox.logLevels.WARN + * @levelMax The default log level for this appender, by default it is 5. Optional. ex: LogBox.logLevels.WARN + */ + function init( + required name, + struct properties={}, + layout="", + levelMin=0, + levelMax=4 + ){ + // Appender Unique ID */ + variables._hash = createObject( 'java', 'java.lang.System' ).identityHashCode( this ); + // Flag denoting if the appender is inited or not. This will be set by LogBox upon succesful creation and registration. + variables.initialized = false; + + // Appender's Name + variables.name = REreplacenocase( arguments.name, "[^0-9a-z]", "", "ALL" ); + + // Set internal properties + variables.properties = arguments.properties; + + // Custom Renderer For Messages + variables.customLayout = ""; + if( len( trim( arguments.layout ) ) ){ + variables.customLayout = createObject( "component", arguments.layout ).init( this ); + } + + // Levels + variables.levelMin = arguments.levelMin; + variables.levelMax = arguments.levelMax; + + return this; + } + + /** + * Runs after the appender has been created and registered. Implemented by Concrete appender + */ + AbstractAppender function onRegistration(){ + return this; + } + + /** + * Runs before the appender is unregistered from LogBox. Implemented by Concrete appender + */ + AbstractAppender function onUnRegistration(){ + return this; + } + + /** + * Setter for level min + * + * @throws AbstractAppender.InvalidLogLevelException + */ + AbstractAppender function setLevelMin( required levelMin ){ + // Verify level + if( this.logLevels.isLevelValid( arguments.levelMin ) AND arguments.levelMin lte getLevelMax() ){ + variables.levelMin = arguments.levelMin; + return this; + } else { + throw( + message = "Invalid Log Level", + detail = "The log level #arguments.levelMin# is invalid or greater than the levelMax (#getLevelMax()#). Valid log levels are from 0 to 5", + type = "AbstractAppender.InvalidLogLevelException" + ); + } + } + + /** + * Setter for level max + * + * @throws AbstractAppender.InvalidLogLevelException + */ + AbstractAppender function setLevelMax( required levelMax ){ + // Verify level + if( this.logLevels.isLevelValid( arguments.levelMax ) AND arguments.levelMax gte getLevelMin() ){ + variables.levelMax = arguments.levelMax; + return this; + } else { + throw( + message = "Invalid Log Level", + detail = "The log level #arguments.levelMax# is invalid or less than the levelMin (#getLevelMin()#). Valid log levels are from 0 to 5", + type = "AbstractAppender.InvalidLogLevelException" + ); + } + } + + /** + * Verify if we have a custom layout object linked + */ + boolean function hasCustomLayout(){ + return isObject( variables.customLayout ); + } + + /** + * convert a severity to a string + * + * @severity The severity to convert to a string + */ + function severityToString( required numeric severity){ + return this.logLevels.lookup( arguments.severity ); + } + + /** + * Get internal hash id + */ + function getHash(){ + return variables._hash; + } + + /** + * Is appender initialized + */ + boolean function isInitialized(){ + return variables.initialized; + } + + /** + * Write an entry into the appender. You must implement this method yourself. + * + * @logEvent The logging event to log + */ + AbstractAppender function logMessage( required wirebox.system.logging.LogEvent logEvent ){ + return this; + } + + /** + * Checks wether a log can be made on this appender using a passed in level + * + * @level The level to check + */ + boolean function canLog( required numeric level ){ + return ( arguments.level GTE getLevelMin() AND arguments.level LTE getLevelMax() ); + } + + /** + * Get a property from the `properties` struct + * + * @property The property key + */ + function getProperty( required property ){ + return variables.properties[ arguments.property ]; + } + + /** + * Set a property from the `properties` struct + * + * @property The property key + * @value The value of the property + */ + AbstractAppender function setProperty( required property, required value ){ + variables.properties[ arguments.property ] = arguments.value; + return this; + } + + /** + * Validate a property from the `properties` struct + * + * @property The property key + */ + boolean function propertyExists( required property ){ + return structKeyExists( variables.properties, arguments.property ); + } + + /****************************************** PRIVATE *********************************************/ + + /** + * Get the ColdBox Utility object + */ + private function getUtil(){ + if( structKeyExists( variables, "util" ) ){ return variables.util; } + variables.util = new wirebox.system.core.util.Util(); + return variables.util; + } + + /** + * Facade to internal ColdFusion logging facilities, just in case. + */ + private AbstractAppender function $log( required severity, required message ){ + cflog( type=arguments.severity, file="LogBox", text=arguments.message ); + return this; + } + + /** + * Utiliy to send to output to console. + * + * @message Message to send + * @addNewLine Add a line break or not, default is yes + */ + private function out( required message, boolean addNewLine=true ){ + if( arguments.addNewLine ){ + arguments.message &= chr( 13 ) & chr( 10 ); + } + createObject( "java", "java.lang.System" ).out.println( arguments.message ); + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/logging/Layout.cfc b/src/cfml/system/_wirebox/system/logging/Layout.cfc new file mode 100644 index 000000000..55006291d --- /dev/null +++ b/src/cfml/system/_wirebox/system/logging/Layout.cfc @@ -0,0 +1,40 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* This is a base layout object that will help you create custom layout's for messages in appenders +**/ +component accessors="true"{ + /** + * The LogBox appender this layotu is linked to. + */ + property name="appender"; + + // The log levels enum as a public property + this.logLevels = new wirebox.system.logging.LogLevels(); + // A line Sep Constant, man, wish we had final in CF. + this.LINE_SEP = chr(13) & chr(10); + + /** + * Constructor + * + * @appender The appender this layout is linked to. + */ + function init( required appender ){ + variables.appender = arguments.appender; + return this; + } + + /** + * Format a logging event message into your own format + * + * @logEvent The LogBox logging event object + */ + function format( required logEvent ){ + throw( + message = "You must implement this layout's format() method", + type = "FormatNotImplementedException" + ) + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/logging/LogBox.cfc b/src/cfml/system/_wirebox/system/logging/LogBox.cfc new file mode 100644 index 000000000..1da0d56d9 --- /dev/null +++ b/src/cfml/system/_wirebox/system/logging/LogBox.cfc @@ -0,0 +1,306 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * This is LogBox, an enterprise logging library. Please remember to persist this class once it has been created. + * You can create as many instances of LogBox as you like. Just remember that you + * need to register loggers in it. It can be one or 1000, it all depends on you. + * + * By default, LogBox will log any warnings pertaining to itself in the CF logs + * according to its name. +*/ +component accessors="true"{ + + + /** + * The LogBox unique ID + */ + property name="logBoxID"; + + /** + * The LogBox operating version + */ + property name="version"; + + /** + * The appender registration map + */ + property name="appenderRegistry" type="struct"; + + /** + * The Logger registration map + */ + property name="loggerRegistry" type="struct"; + + /** + * Category based appenders + */ + property name="categoryAppenders"; + + /** + * Configuration object + */ + property name="config"; + + /** + * ColdBox linkage class + */ + property name="coldbox"; + + // The log levels enum as a public property + this.logLevels = new wirebox.system.logging.LogLevels(); + + /** + * Constructor + * + * @config The LogBoxConfig object to use to configure this instance of LogBox + * @coldbox A coldbox application that this instance of logbox can be linked to. + */ + function init( required wirebox.system.logging.config.LogBoxConfig config, coldbox="" ){ + // LogBox Unique ID + variables.logboxID = createObject( 'java', 'java.lang.System' ).identityHashCode( this ); + // Appenders + variables.appenderRegistry = structnew(); + // Loggers + variables.loggerRegistry = structnew(); + // Category Appenders + variables.categoryAppenders = ""; + // Version + variables.version = "5.1.4+741"; + + // Link incoming ColdBox argument + variables.coldbox = arguments.coldbox; + + // Configure LogBox + configure( arguments.config ); + + return this; + } + + /** + * Configure logbox for operation. You can also re-configure LogBox programmatically. Basically we register all appenders here and all categories + * + * @config The LogBoxConfig object to use to configure this instance of LogBox: wirebox.system.logging.config.LogBoxConfig + * @config.doc_generic wirebox.system.logging.config.LogBoxConfig + */ + function configure( required config ){ + lock name="#variables.logBoxID#.logbox.config" type="exclusive" timeout="30" throwOnTimeout=true{ + // Store config object with validation + variables.config = arguments.config.validate(); + + // Reset Registries + variables.appenderRegistry = structnew(); + variables.loggerRegistry = structnew(); + + //Get appender definitions + var appenders = variables.config.getAllAppenders(); + + // Register All Appenders configured + for( var key in appenders ){ + registerAppender( argumentCollection=appenders[ key ] ); + } + + // Get Root def + var rootConfig = variables.config.getRoot(); + // Create Root Logger + var args = { + category = "ROOT", + levelMin = rootConfig.levelMin, + levelMax = rootConfig.levelMax, + appenders = getAppendersMap( rootConfig.appenders ) + }; + + //Save in Registry + variables.loggerRegistry = { + "ROOT" = new wirebox.system.logging.Logger( argumentCollection=args ) + }; + } + } + + /** + * Get the root logger object + * + * @return wirebox.system.logging.Logger + */ + function getRootLogger(){ + return variables.loggerRegistry[ "ROOT" ]; + } + + /** + * Get a logger object configured with a category name and appenders. If not configured, then it reverts to the root logger defined for this instance of LogBox + * + * @category The category name to use in this logger or pass in the target object will log from and we will inspect the object and use its metadata name + * + * @return wirebox.system.logging.Logger + */ + function getLogger( required category ){ + var root = getRootLogger(); + + // is category object? + if( isObject( arguments.category ) ){ + arguments.category = getMetadata( arguments.category ).name; + } + + // trim cat, just in case + arguments.category = trim( arguments.category ); + + // Is logger by category name created already? + if( structKeyExists( variables.loggerRegistry, arguments.category ) ){ + return variables.loggerRegistry[ arguments.category ]; + } + + // Do we have a category definition, so we can build it? + var args = {}; + if( variables.config.categoryExists( arguments.category ) ){ + var categoryConfig = variables.config.getCategory( arguments.category ); + // Setup creation arguments + args = { + category = categoryConfig.name, + levelMin = categoryConfig.levelMin, + levelMax = categoryConfig.levelMax, + appenders = getAppendersMap( categoryConfig.appenders ) + }; + } else { + // Do Category Inheritance? or else just return the root logger. + root = locateCategoryParentLogger( arguments.category ); + // Build it out as per Root logger + args = { + category = arguments.category, + levelMin = root.getLevelMin(), + levelMax = root.getLevelMax() + }; + } + + // Create it + lock name="#variables.logboxID#.logBox.logger.#arguments.category#" type="exclusive" throwontimeout="true" timeout="30"{ + if( NOT structKeyExists( variables.loggerRegistry, arguments.category ) ){ + // Create logger + var oLogger = new wirebox.system.logging.Logger( argumentCollection=args ); + // Inject Root Logger + oLogger.setRootLogger( root ); + // Store it + variables.loggerRegistry[ arguments.category ] = oLogger; + } + } + + return variables.loggerRegistry[ arguments.category ]; + } + + /** + * Get the list of currently instantiated loggers. + */ + string function getCurrentLoggers(){ + return structKeyList( variables.loggerRegistry ); + } + + /** + * Get the list of currently instantiated appenders. + */ + string function getCurrentAppenders(){ + return structKeyList( variables.appenderRegistry ); + } + + /** + * Register a new appender object in the appender registry. + * + * @name A unique name for the appender to register. Only unique names can be registered per variables. + * @class The appender's class to register. We will create, init it and register it for you. + * @properties The structure of properties to configure this appender with. + * @layout The layout class to use in this appender for custom message rendering + * @levelMin The default log level for this appender, by default it is 0. Optional. ex: LogBox.logLevels.WARN + * @levelMax The default log level for this appender, by default it is 4. Optional. ex: LogBox.logLevels.WARN + */ + function registerAppender( + required name, + required class, + struct properties={}, + layout="", + numeric levelMin=0, + numeric levelMax=4 + ){ + + if( !structKeyExists( variables.appenderRegistry, arguments.name ) ){ + + lock name="#variables.logboxID#.registerappender.#name#" type="exclusive" timeout="15" throwOnTimeout="true"{ + + if( !structKeyExists( variables.appenderRegistry, arguments.name ) ){ + + // Create appender and linking + var oAppender = new "#arguments.class#"( argumentCollection=arguments ); + oAppender.setColdBox( variables.coldbox ); + // run registration event + oAppender.onRegistration(); + // set initialized + oAppender.setInitialized( true ); + // Store it + variables.appenderRegistry[ arguments.name ] = oAppender; + + } + + } // end lock + + } + + } + + /********************************************* PRIVATE *********************************************/ + + /** + * Get a parent logger according to category convention inheritance. If not found, it returns the root logger. + * + * @category The category name to investigate for parents + */ + private function locateCategoryParentLogger( required category ){ + // Get parent category name shortened by one. + var parentCategory = ""; + + // category len check + if( len( arguments.category ) ){ + parentCategory = listDeleteAt( arguments.category, listLen( arguments.category, "." ), "." ); + } + + // Check if parent Category is empty + if( len( parentCategory ) EQ 0 ){ + // Just return the root logger, nothing found. + return getRootLogger(); + } + // Does it exist already in the instantiated loggers? + if( structKeyExists( variables.loggerRegistry, parentCategory ) ){ + return variables.loggerRegistry[ parentCategory ]; + } + // Do we need to create it, lazy loading? + if( variables.config.categoryExists( parentCategory ) ){ + return getLogger( parentCategory ); + } + // Else, it was not located, recurse + return locateCategoryParentLogger( parentCategory ); + } + + /** + * Get a map of appenders by list. Usually called to get a category of appenders + * + * @appenders The list of appenders to get + */ + struct function getAppendersMap( required appenders ){ + var results = arguments.appenders + .listToArray() + .reduce( function( result, item, index ){ + var target = {}; + if( !isNull( arguments.result ) ){ + target = result; + } + target[ item ] = variables.appenderRegistry[ item ]; + return target; + } ); + + return ( isNull( results ) ? structNew() : results ); + } + + /** + * Get Utility Object + */ + private function getUtil(){ + return new wirebox.system.core.util.Util(); + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/logging/LogEvent.cfc b/src/cfml/system/_wirebox/system/logging/LogEvent.cfc new file mode 100644 index 000000000..f20b04217 --- /dev/null +++ b/src/cfml/system/_wirebox/system/logging/LogEvent.cfc @@ -0,0 +1,81 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* This resembles a logging event within LogBox +**/ +component accessors="true"{ + + + /** + * The category to log messages under + */ + property name="category" default=""; + + /** + * The timestamp of the log + */ + property name="timestamp"; + + /** + * The message to log + */ + property name="message" default=""; + + /** + * The severity to log with + */ + property name="severity" default=""; + + /** + * Any extra info to log + */ + property name="extrainfo" default=""; + + /** + * Constructor + * + * @message The message to log. + * @severity The severity level to log. + * @extraInfo Extra information to send to the loggers. + * @category The category to log this message under. By default it is blank. + */ + function init( required message, required severity, extraInfo="", category="" ){ + // Init event + variables.timestamp = now(); + // converters + variables.xmlConverter = new wirebox.system.core.conversion.XMLConverter(); + + for( var key in arguments ){ + if( isSimpleValue( arguments[ key ] ) ){ + arguments[ key ] = trim( arguments[ key ] ); + } + variables[ key ] = arguments[ key ]; + } + return this; + } + + /** + * Get the extra info as a string representation + */ + function getExtraInfoAsString(){ + // Simple value, just return it + if( isSimpleValue( variables.extraInfo ) ){ + return variables.extraInfo; + } + + // Convention translation: $toString(); + if( isObject( variables.extraInfo ) AND structKeyExists( variables.extraInfo, "$toString" ) ){ + return variables.extraInfo.$toString(); + } + + // Component XML conversion + if( isObject( variables.extraInfo ) ){ + return variables.xmlConverter.toXML( variables.extraInfo ); + } + + // Complex values, return serialized in json + return serializeJSON( variables.extraInfo ); + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/logging/LogLevels.cfc b/src/cfml/system/_wirebox/system/logging/LogLevels.cfc new file mode 100644 index 000000000..114540304 --- /dev/null +++ b/src/cfml/system/_wirebox/system/logging/LogLevels.cfc @@ -0,0 +1,85 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* The different logging levels available in LogBox. Log levels available in the this scope: OFF=-1, FATAL=0, ERROR=1, WARN=2, INFO=3, DEBUG=4 +* @author Luis Majano +*/ +component{ + + // All Available Logging Levels for LogBox + this.OFF = -1; + this.FATAL = 0; + this.ERROR = 1; + this.WARN = 2; + this.INFO = 3; + this.DEBUG = 4; + + // List of valid levels + this.VALIDLEVELS = "off,fatal,error,warn,info,debug"; + + // Max + this.MINLEVEL = -1; + this.MAXLEVEL = 4; + + /** + * Lookup a level in our numeric enum, else it returns void. + * + * @level The numeric level + */ + function lookup( required level ){ + switch( level ){ + case -1: return "OFF"; + case 0: return "FATAL"; + case 1: return "ERROR"; + case 2: return "WARN"; + case 3: return "INFO"; + case 4: return "DEBUG"; + } + } + + /** + * Lookup level in numeric format from a string. If not found a 999 is returned + * + * @level The string level + */ + function lookupAsInt( required level ){ + switch( level ){ + case "OFF" : return -1; + case "FATAL" : return 0; + case "ERROR" : return 1; + case "WARN" : return 2; + case "WARNING" : return 2; + case "INFO" : return 3; + case "INFORMATION" : return 3; + case "DEBUG" : return 4; + default : return 999; + } + } + + /** + * Lookup a CF level using a number + * + * @level Numeric level + */ + function lookupCF( required level ){ + switch( level ){ + case -1: return "OFF"; + case 0: return "Fatal"; + case 1: return "Error"; + case 2: return "Warning"; + case 3: return "Information"; + case 4: return "Information"; + default: return "Information"; + } + } + + /** + * Verifies if a level is valid or not + * + * @level numeric level + */ + function isLevelValid( required level ){ + return ( arguments.level gte this.MINLEVEL AND arguments.level lte this.MAXLEVEL ); + } +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/logging/Logger.cfc b/src/cfml/system/_wirebox/system/logging/Logger.cfc new file mode 100644 index 000000000..8c43b3ee6 --- /dev/null +++ b/src/cfml/system/_wirebox/system/logging/Logger.cfc @@ -0,0 +1,423 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* This is a logging object that allows for all kinds of logging to occur within its appender +**/ +component accessors="true"{ + + /** + * Root Logger reference + */ + property name="rootLogger"; + + /** + * Category for this logger + */ + property name="category"; + + /** + * Appenders linked to this logger + */ + property name="appenders" type="struct"; + + /** + * Level Min + */ + property name="levelMin"; + + /** + * Level Max + */ + property name="levelMax"; + + // The log levels enum as a public property + this.logLevels = new wirebox.system.logging.LogLevels(); + + /** + * Constructor + * + * @category The category name to use this logger with + * @levelMin The default log level for this appender, by default it is 0. Optional. ex: LogBox.logLevels.WARN + * @levelMax The default log level for this appender, by default it is 0. Optional. ex: LogBox.logLevels.WARN + * @appenders A struct of already created appenders for this category, or blank to use the root logger. + */ + function init( + required category, + numeric levelMin=0, + numeric levelMax=4, + struct appenders={} + ){ + // Save Properties + variables.rootLogger = ""; + variables.category = arguments.category; + variables.appenders = arguments.appenders; + + // Logger Logging Level defaults, which is wideeeee open! + variables.levelMin = arguments.levelMin; + variables.levelMax = arguments.levelMax; + + // Utilities + variables._hash = createObject( 'java','java.lang.System').identityHashCode( this ); + variables.util = new wirebox.system.core.util.Util(); + + + // Local Locking + variables.lockName = variables._hash & "LoggerOperation"; + variables.lockTimeout = 20; + + return this; + } + + /** + * Do we have any appenders + */ + function hasAppenders(){ + return !variables.appenders.isEmpty(); + } + + /** + * Stupid ACF10 not working on getters + */ + function getAppenders(){ + return variables.appenders; + } + + /** + * Get an appender reference, if the appender does not exist it will throw an exception + * + * @name The appender's name + * + * @throws Logger.AppenderNotFound + */ + function getAppender( required name ){ + if( structKeyExists( variables.appenders, arguments.name ) ){ + return variables.appenders[ arguments.name ]; + } + + throw( + message = "Appender #arguments.name# does not exist.", + detail = "The appenders registered are #structKeyList( variables.appenders )#", + type = "Logger.AppenderNotFound" + ); + } + + /** + * Check if an appender exists + * + * @name The name of the appender + */ + boolean function appenderExists( required name ){ + return variables.appenders.keyExists( arguments.name ); + } + + /** + * Add a new appender to the list of appenders for this logger. If the appender already exists, then it will not be added. + * + * @newAppender An appender object reference + * + * @throws Logger.InvalidAppenderNameException + */ + Logger function addAppender( required newAppender ){ + //Verify Appender's name + if( NOT len( arguments.newAppender.getName() ) ){ + throw( + message = "Appender does not have a name, please instantiate the appender with a unique name.", + type = "Logger.InvalidAppenderNameException" + ); + } + + // Get name + var name = arguments.newAppender.getName(); + if( ! appenderExists( name ) ){ + lock name="#variables._hash#.registerappender.#name#" type="exclusive" timeout="15" throwOnTimeout="true"{ + if( ! appenderExists( name ) ){ + // run registration event if not Initialized + if( NOT arguments.newAppender.isInitialized() ){ + arguments.newAppender.onRegistration(); + arguments.newAppender.setInitialized( true ); + } + // Store Appender + variables.appenders[ name ] = arguments.newAppender; + } + } + } + + return this; + } + + /** + * Unregister an appender from this Logger. True if successful or false otherwise. + */ + boolean function removeAppender( required name ){ + var isRemoved = false; + + if( appenderExists( arguments.name ) ){ + lock name="#variables._hash#.registerappender.#arguments.name#" type="exclusive" timeout="15" throwOnTimeout="true"{ + if( appenderExists( name ) ){ + // Get Appender + var oAppender = variables.appenders [arguments.name ]; + // Run un-registration event + oAppender.onUnRegistration(); + // Now Delete it + structDelete( variables.appenders, arguments.name ); + // flag deletion. + isRemoved = true; + } + } + } + + return isRemoved; + } + + /** + * Remove all appenders registered + */ + Logger function removeAllAppenders(){ + var appenderKeys = structKeyArray( variables.appenders ); + + appenderKeys.each( function( item ){ + removeAppender( item ); + } ); + + return this; + } + + /** + * Set the min level + * + * @levelMin the level to set + * + * @throws Logger.InvalidLogLevelException + */ + Logger function setLevelMin( required levelMin ){ + // convert to numeric, if passed in string like "INFO" + if ( ! isNumeric( arguments.levelMin ) ) { + arguments.levelMin = this.logLevels.lookupAsInt( arguments.levelMin ); + } + // Verify level + if( this.logLevels.isLevelValid( arguments.levelMin ) AND + arguments.levelMin lte getLevelMax() + ){ + variables.levelMin = arguments.levelMin; + } else { + throw( + message = "Invalid Log Level", + detail = "The log level #arguments.levelMin# is invalid or greater than the levelMax (#getLevelMax()#). Valid log levels are from 0 to 5", + type = "Logger.InvalidLogLevelException" + ); + } + + return this; + } + + /** + * Set the max level + * + * @levelMax the level to set + * + * @throws Logger.InvalidLogLevelException + */ + Logger function setLevelMax( required levelMax ){ + // convert to numeric, if passed in string like "INFO" + if ( ! isNumeric( arguments.levelMax ) ) { + arguments.levelMax = this.logLevels.lookupAsInt( arguments.levelMax ); + } + // Verify level + if( this.logLevels.isLevelValid( arguments.levelMax ) AND + arguments.levelMax gte getLevelMin() + ){ + variables.levelMax = arguments.levelMax; + } else { + throw( + message = "Invalid Log Level", + detail = "The log level #arguments.levelMax# is invalid or less than the levelMin (#getLevelMin()#). Valid log levels are from 0 to 5", + type = "Logger.InvalidLogLevelException" + ); + } + + return this; + } + + /** + * Log a debug message + * + * @message The message to log + * @extraInfo Extra information to send to appenders + * + * @return Logger + */ + function debug( required message, extraInfo="" ){ + arguments.severity = this.logLevels.DEBUG; + return logMessage( argumentCollection=arguments ); + } + + /** + * Log a info message + * + * @message The message to log + * @extraInfo Extra information to send to appenders + * + * @return Logger + */ + function info( required message, extraInfo="" ){ + arguments.severity = this.logLevels.INFO; + return logMessage( argumentCollection=arguments ); + } + + /** + * Log a warn message + * + * @message The message to log + * @extraInfo Extra information to send to appenders + * + * @return Logger + */ + function warn( required message, extraInfo="" ){ + arguments.severity = this.logLevels.WARN; + return logMessage( argumentCollection=arguments ); + } + + /** + * Log an error message + * + * @message The message to log + * @extraInfo Extra information to send to appenders + * + * @return Logger + */ + function error( required message, extraInfo="" ){ + arguments.severity = this.logLevels.ERROR; + return logMessage( argumentCollection=arguments ); + } + + /** + * Log a fatal message + * + * @message The message to log + * @extraInfo Extra information to send to appenders + * + * @return Logger + */ + function fatal( required message, extraInfo="" ){ + arguments.severity = this.logLevels.FATAL; + return logMessage( argumentCollection=arguments ); + } + + /** + * Write an entry into the loggers registered with this LogBox variables + * + * @message The message to log + * @severity The severity level to log, if invalid, it will default to **Info** + * @extraInfo Extra information to send to appenders + */ + Logger function logMessage( required message, required severity, extraInfo="" ){ + var target = this; + + // Verify severity, if invalid, default to INFO + if( NOT this.logLevels.isLevelValid( arguments.severity ) ){ + arguments.severity = this.logLevels.INFO; + } + + // If message empty, just exit + arguments.message = trim( arguments.message ); + if( NOT len( arguments.message ) ){ + return this; + } + + //Is Logging Enabled? + if( getLevelMin() eq this.logLevels.OFF ){ + return this; + } + + // Can we log on target + if( canLog( arguments.severity ) ){ + // Create Logging Event + arguments.category = target.getCategory(); + var logEvent = new wirebox.system.logging.LogEvent( argumentCollection=arguments ); + + // Do we have appenders locally? or go to root Logger + if( NOT hasAppenders() ){ + target = getRootLogger(); + } + // Get appenders + var appenders = target.getAppenders(); + // Delegate Calls to appenders + for( var key in appenders ){ + // Get Appender + var thisAppender = appenders[ key ]; + // Log the message in the appender if the appender allows it + if( thisAppender.canLog( arguments.severity ) ){ + // check to see if the async property was passed during definition + if( thisAppender.propertyExists( 'async' ) && thisAppender.getProperty( 'async' ) ) { + // prepare threading variables. + var threadName = "logMessage_#replace( createUUID(), "-", "", "all" )#"; + // Are we in a thread already? + if( variables.util.inThread() ) { + thisAppender.logMessage( logEvent ); + } else { + // Thread this puppy + thread action="run" name="#threadName#" logEvent="#logEvent#" thisAppender="#thisAppender#"{ + attributes.thisAppender.logMessage( attributes.logEvent ); + } + } + } else { + thisAppender.logMessage( logEvent ); + } + } + } + } + + return this; + } + + /** + * Checks wether a log can be made on this Logger using a passed in level + * + * @level The numeric or string level representation + */ + boolean function canLog( required level ){ + // If numeric, do a comparison immediately. + if( isNumeric( arguments.level ) ){ + return ( arguments.level GTE getLevelMin() AND arguments.level LTE getLevelMax() ); + } + // Else it is a string + return ( canLog( this.LogLevels.lookupAsInt( arguments.level ) ) ); + } + + /** + * Can I log a fatal message + */ + boolean function canFatal(){ + return canLog( this.logLevels.FATAL ); + } + + /** + * Can I log a ERROR message + */ + boolean function canError(){ + return canLog( this.logLevels.ERROR ); + } + + /** + * Can I log a WARN message + */ + boolean function canWarn(){ + return canLog( this.logLevels.WARN ); + } + + /** + * Can I log a INFO message + */ + boolean function canInfo(){ + return canLog( this.logLevels.INFO ); + } + + /** + * Can I log a DEBUG message + */ + boolean function canDebug(){ + return canLog( this.logLevels.DEBUG ); + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/logging/appenders/CFAppender.cfc b/src/cfml/system/_wirebox/system/logging/appenders/CFAppender.cfc new file mode 100644 index 000000000..0d3335e4e --- /dev/null +++ b/src/cfml/system/_wirebox/system/logging/appenders/CFAppender.cfc @@ -0,0 +1,83 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * A ColdFusion Appender + * - logType : file or application + * - fileName : The log file name to use, else uses the appender's name +**/ +component accessors="true" extends="wirebox.system.logging.AbstractAppender"{ + + /** + * Constructor + * + * @name The unique name for this appender. + * @properties A map of configuration properties for the appender" + * @layout The layout class to use in this appender for custom message rendering. + * @levelMin The default log level for this appender, by default it is 0. Optional. ex: LogBox.logLevels.WARN + * @levelMax The default log level for this appender, by default it is 5. Optional. ex: LogBox.logLevels.WARN + * + * @throws CFAppender.InvalidLogTypeException + */ + function init( + required name, + struct properties={}, + layout="", + levelMin=0, + levelMax=4 + ){ + // Init supertype + super.init( argumentCollection=arguments ); + + // Verify properties + if( NOT propertyExists( 'logType' ) ){ + setProperty( "logType", "file" ); + } else { + // Check types + if( NOT reFindNoCase( "^(file|application)$", getProperty( "logType" ) ) ){ + throw( + message = "Invalid logtype choosen #getProperty("logType")#", + detail = "Valid types are file or application", + type = "CFAppender.InvalidLogTypeException" + ); + } + } + if( NOT propertyExists( "fileName" ) ){ + setProperty( "fileName", getName() ); + } + + return this; + } + + /** + * Write an entry into the appender. You must implement this method yourself. + * + * @logEvent The logging event to log + */ + function logMessage( required wirebox.system.logging.LogEvent logEvent ){ + var entry = ""; + + if( hasCustomLayout() ){ + entry = getCustomLayout().format( arguments.logEvent ); + } else { + entry = "#arguments.logEvent.getCategory()# #arguments.logEvent.getMessage()# ExtraInfo: #arguments.logEvent.getextraInfoAsString()#"; + } + + if( getProperty( "logType" ) == "file" ){ + cflog( + file = getProperty( "fileName" ), + type = "#this.logLevels.lookupCF( arguments.logEvent.getSeverity() )#", + text = entry + ); + } else { + cflog( + file = "Application", + type = "#this.logLevels.lookupCF( arguments.logEvent.getSeverity() )#", + text = entry + ); + } + + return this; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/logging/appenders/ConsoleAppender.cfc b/src/cfml/system/_wirebox/system/logging/appenders/ConsoleAppender.cfc new file mode 100644 index 000000000..4408dcf56 --- /dev/null +++ b/src/cfml/system/_wirebox/system/logging/appenders/ConsoleAppender.cfc @@ -0,0 +1,65 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* Console Appender +*/ +component extends="wirebox.system.logging.AbstractAppender"{ + + /** + * Constructor + * + * @name The unique name for this appender. + * @properties A map of configuration properties for the appender. + * @layout The layout class to use in this appender for custom message rendering. + * @levelMin The default log level for this appender, by default it is 0. Optional. ex: LogBox.logLevels.WARN + * @levelMax The default log level for this appender, by default it is 4. Optional. ex: LogBox.logLevels.WARN + */ + public ConsoleAppender function init( + required string name, + struct properties = {}, + string layout = "", + string levelMin = 0, + string levelMax = 4 + ){ + super.init( argumentCollection=arguments ); + variables.out = createObject( "java", "java.lang.System" ).out; + variables.error = createObject( "java", "java.lang.System" ).err; + return this; + } + + /** + * Write entry into the appender + * + * @logEvent The logging event. + */ + function logMessage( required any logEvent ) { + var entry = ""; + if( hasCustomLayout() ){ + entry = getCustomLayout().format( logEvent ); + } else { + entry = severityToString( logEvent.getseverity() ) & " " & + logEvent.getCategory() & " " & + logEvent.getmessage() & " ExtraInfo: " & + logEvent.getextraInfoAsString(); + } + switch( logEvent.getSeverity() ){ + // Fatal + Error go to error stream + case "0" : case "1" : { + // log message + variables.error.println( entry ); + break; + } + // Warning and above go to info stream + default : { + // log message + variables.out.println( entry ); + break; + } + } + + + return this; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/logging/appenders/DBAppender.cfc b/src/cfml/system/_wirebox/system/logging/appenders/DBAppender.cfc new file mode 100644 index 000000000..bbc6fa4e6 --- /dev/null +++ b/src/cfml/system/_wirebox/system/logging/appenders/DBAppender.cfc @@ -0,0 +1,352 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * A simple DB appender for MySQL, MSSQL, Oracle, PostgreSQL + * + * Properties: + * - dsn : the dsn to use for logging + * - table : the table to store the logs in + * - schema : which schema the table exists in (Optional) + * - columnMap : A column map for aliasing columns. (Optional) + * - autocreate : if true, then we will create the table. Defaults to false (Optional) + * - ensureChecks : if true, then we will check the dsn and table existence. Defaults to true (Optional) + * + * The columns needed in the table are + * + * - id : UUID + * - severity : string + * - category : string + * - logdate : timestamp + * - appendername : string + * - message : string + * - extrainfo : string + * + * If you are building a mapper, the map must have the above keys in it. +**/ +component accessors="true" extends="wirebox.system.logging.AbstractAppender" { + + /** + * Constructor + * + * @name The unique name for this appender. + * @properties A map of configuration properties for the appender" + * @layout The layout class to use in this appender for custom message rendering. + * @levelMin The default log level for this appender, by default it is 0. Optional. ex: LogBox.logLevels.WARN + * @levelMax The default log level for this appender, by default it is 5. Optional. ex: LogBox.logLevels.WARN + */ + function init( + required name, + struct properties={}, + layout="", + levelMin=0, + levelMax=4 + ){ + // Init supertype + super.init( argumentCollection=arguments ); + + // valid columns + variables.columns = "id,severity,category,logdate,appendername,message,extrainfo"; + // UUID generator + variables.uuid = createobject( "java", "java.util.UUID" ); + + // Verify properties + if( NOT propertyExists( 'dsn' ) ){ + throw( message="No dsn property defined", type="DBAppender.InvalidProperty" ); + } + if( NOT propertyExists( 'table' ) ){ + throw( message="No table property defined", type="DBAppender.InvalidProperty" ); + } + if( NOT propertyExists( 'autoCreate' ) OR NOT isBoolean( getProperty( 'autoCreate' ) ) ){ + setProperty( 'autoCreate', false ); + } + if( NOT propertyExists( 'defaultCategory' ) ){ + setProperty( "defaultCategory", arguments.name ); + } + if( propertyExists( "columnMap" ) ){ + checkColumnMap(); + } + if( NOT propertyExists( "ensureChecks" ) ){ + setProperty( "ensureChecks", true ); + } + if( NOT propertyExists( "rotate" ) ){ + setProperty( "rotate", true ); + } + if( NOT propertyExists( "rotationDays" ) ){ + setProperty( "rotationDays", 30 ); + } + if( NOT propertyExists( "rotationFrequency" ) ){ + setProperty( "rotationFrequency", 5 ); + } + if( NOT propertyExists( "schema" ) ){ + setProperty( "schema", "" ); + } + + // DB Rotation Time + variables.lastDBRotation = ""; + return this; + } + + /** + * Runs on registration + */ + function onRegistration(){ + if( getProperty( "ensureChecks" ) ){ + // Table Checks + ensureTable(); + } + return this; + } + + /** + * Write an entry into the appender. You must implement this method yourself. + * + * @logEvent The logging event to log + */ + function logMessage( required wirebox.system.logging.LogEvent logEvent ){ + var type = "cf_sql_tinyint"; + var category = getProperty( "defaultCategory" ); + var cmap = ""; + var cols = ""; + var loge = arguments.logEvent; + var message = loge.getMessage(); + + // Check Category Sent? + if( NOT loge.getCategory() eq "" ){ + category = loge.getCategory(); + } + + // Column Maps + if( propertyExists( 'columnMap' ) ){ + cmap = getProperty( 'columnMap' ); + cols = "#cmap.id#,#cmap.severity#,#cmap.category#,#cmap.logdate#,#cmap.appendername#,#cmap.message#,#cmap.extrainfo#"; + } else { + cols = variables.columns; + } + + queryExecute( + "INSERT INTO #getTable()# (#cols#) + VALUES ( + :uuid, + :severity, + :category, + :timestamp, + :name, + :message, + :extraInfo + ) + ", + { + uuid = { cfsqltype="cf_sql_varchar", value="#variables.uuid.randomUUID().toString()#" }, + severity = { cfsqltype="cf_sql_varchar", value="#severityToString( loge.getseverity() )#" }, + category = { cfsqltype="cf_sql_varchar", value="#left( category, 100 )#" }, + timestamp = { cfsqltype="cf_sql_timestamp", value="#loge.getTimestamp()#" }, + name = { cfsqltype="cf_sql_varchar", value="#left( getName(), 100 )#" }, + message = { cfsqltype="cf_sql_varchar", value="#loge.getMessage()#" }, + extraInfo = { cfsqltype="cf_sql_varchar", value="#loge.getExtraInfoAsString()#" } + }, + { + datasource = getProperty( "dsn" ) + } + ); + + this.rotationCheck(); + + return this; + } + + /** + * Rotation checks + */ + function rotationCheck(){ + // Verify if in rotation frequency + if( isDate( variables.lastDBRotation ) AND dateDiff( "n", variables.lastDBRotation, now() ) LTE getProperty( "rotationFrequency" ) ){ + return; + } + + // Rotations + this.doRotation(); + + // Store last profile time + variables.lastDBRotation = now(); + } + + /** + * Do the rotation + */ + function doRotation(){ + var qLogs = ""; + var cols = variables.columns; + var targetDate = dateAdd( "d", "-#getProperty( "rotationDays" )#", now() ); + + queryExecute( + "DELETE + FROM #getTable()# + WHERE #listgetAt( cols,4)# < :datetime + ", + { + datetime = { cfsqltype="#getDateTimeDBType()#", value="#dateFormat( targetDate, 'mm/dd/yyyy' )#" } + }, + { + datasource = getProperty( "dsn" ) + } + ); + + return this; + } + + /************************************************ PRIVATE ************************************************/ + + /** + * Return the table name with the schema included if found. + */ + private function getTable(){ + if( len( getProperty( 'schema' ) ) ){ + return getProperty( 'schema' ) & "." & getProperty( 'table' ); + } + return getProperty( 'table' ); + } + + /** + * Verify or create the logging table + */ + private function ensureTable(){ + var dsn = getProperty( "dsn" ); + var qTables = 0; + var tableFound = false; + var qCreate = ""; + var cols = variables.columns; + + if( getProperty( "autoCreate" ) ){ + // Get Tables on this DSN + cfdbinfo( datasource="#dsn#", name="qTables", type="tables" ); + + for( var thisRecord in qTables ){ + if( thisRecord.table_name == getProperty( "table" ) ){ + tableFound = true; + break; + } + } + + if( NOT tableFound ){ + queryExecute( + "CREATE TABLE #getTable()# ( + #listgetAt( cols, 1 )# VARCHAR(36) NOT NULL, + #listgetAt( cols, 2 )# VARCHAR(10) NOT NULL, + #listgetAt( cols, 3 )# VARCHAR(100) NOT NULL, + #listgetAt( cols, 4 )# #getDateTimeColumnType()# NOT NULL, + #listgetAt( cols, 5 )# VARCHAR(100) NOT NULL, + #listgetAt( cols, 6 )# #getTextColumnType()#, + #listgetAt( cols, 7 )# #getTextColumnType()#, + PRIMARY KEY (id) + )", + {}, + { + datasource = getProperty( "dsn" ) + } + ); + } + } + } + + /** + * Check a column map definition + * + * @throws DBAppender.InvalidColumnMapException + */ + private function checkColumnMap(){ + var map = getProperty( 'columnMap' ); + + for( var key in map ){ + if( NOT listFindNoCase( variables.columns, key ) ){ + throw( + message = "Invalid column map key: #key#", + detail = "The available keys are #variables.columns#", + type = "DBAppender.InvalidColumnMapException" + ); + } + } + } + + /** + * Get db specific date time column type + */ + private function getDateTimeDBType(){ + var qResults = ""; + + cfdbinfo( type="Version", name="qResults", datasource="#getProperty( 'dsn' )#" ); + + switch( qResults.database_productName ){ + case "PostgreSQL" : { + return "cf_sql_timestamp"; + } + case "MySQL" : { + return "cf_sql_timestamp"; + } + case "Microsoft SQL Server" : { + return "cf_sql_date"; + } + case "Oracle" :{ + return "cf_sql_timestamp"; + } + default : { + return "cf_sql_timestamp"; + } + } + } + + /** + * Get db specific text column type + */ + private function getTextColumnType(){ + var qResults = ""; + + cfdbinfo( type="Version", name="qResults", datasource="#getProperty( 'dsn' )#" ); + + switch( qResults.database_productName ){ + case "PostgreSQL" : { + return "TEXT"; + } + case "MySQL" : { + return "LONGTEXT"; + } + case "Microsoft SQL Server" : { + return "TEXT"; + } + case "Oracle" :{ + return "LONGTEXT"; + } + default : { + return "TEXT"; + } + } + } + + /** + * Get db specific text column type + */ + private function getDateTimeColumnType(){ + var qResults = ""; + + cfdbinfo( type="Version", name="qResults", datasource="#getProperty( 'dsn' )#" ); + + switch( qResults.database_productName ){ + case "PostgreSQL" : { + return "TIMESTAMP"; + } + case "MySQL" : { + return "DATETIME"; + } + case "Microsoft SQL Server" : { + return "DATETIME"; + } + case "Oracle" :{ + return "DATE"; + } + default : { + return "DATETIME"; + } + } + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/logging/appenders/DummyAppender.cfc b/src/cfml/system/_wirebox/system/logging/appenders/DummyAppender.cfc new file mode 100644 index 000000000..b26ecde00 --- /dev/null +++ b/src/cfml/system/_wirebox/system/logging/appenders/DummyAppender.cfc @@ -0,0 +1,40 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * A dummy appender that goes nowhere +**/ +component accessors="true" extends="wirebox.system.logging.AbstractAppender" { + + /** + * Constructor + * + * @name The unique name for this appender. + * @properties A map of configuration properties for the appender" + * @layout The layout class to use in this appender for custom message rendering. + * @levelMin The default log level for this appender, by default it is 0. Optional. ex: LogBox.logLevels.WARN + * @levelMax The default log level for this appender, by default it is 5. Optional. ex: LogBox.logLevels.WARN + */ + function init( + required name, + struct properties={}, + layout="", + levelMin=0, + levelMax=4 + ){ + // Init supertype + super.init( argumentCollection=arguments ); + + return this; + } + + /** + * Write an entry into the appender. You must implement this method yourself. + * + * @logEvent The logging event to log + */ + AbstractAppender function logMessage( required wirebox.system.logging.LogEvent logEvent ){ + return this; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/logging/appenders/EmailAppender.cfc b/src/cfml/system/_wirebox/system/logging/appenders/EmailAppender.cfc new file mode 100644 index 000000000..aa40424dd --- /dev/null +++ b/src/cfml/system/_wirebox/system/logging/appenders/EmailAppender.cfc @@ -0,0 +1,150 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * An appender that sends out emails + * + * Properties: + * - subject - Get's pre-pended with the category field. + * - from - required + * - to - required can be a ; list of emails + * - cc + * - bcc + * - mailserver (optional) + * - mailpassword (optional) + * - mailusername (optional) + * - mailport (optional - 25) +**/ +component accessors="true" extends="wirebox.system.logging.AbstractAppender" { + + /** + * Constructor + * + * @name The unique name for this appender. + * @properties A map of configuration properties for the appender" + * @layout The layout class to use in this appender for custom message rendering. + * @levelMin The default log level for this appender, by default it is 0. Optional. ex: LogBox.logLevels.WARN + * @levelMax The default log level for this appender, by default it is 5. Optional. ex: LogBox.logLevels.WARN + */ + function init( + required name, + struct properties={}, + layout="", + levelMin=0, + levelMax=4 + ){ + // Init supertype + super.init( argumentCollection=arguments ); + + // Property Checks + if( NOT propertyExists( "from" ) ){ + throw(message="from email is required",type="EmailAppender.PropertyNotFound" ); + } + if( NOT propertyExists( "to" ) ){ + throw(message="to email(s) is required",type="EmailAppender.PropertyNotFound" ); + } + if( NOT propertyExists( "subject" ) ){ + throw(message="subject is required",type="EmailAppender.PropertyNotFound" ); + } + if( NOT propertyExists( "cc" ) ){ + setProperty( "cc","" ); + } + if( NOT propertyExists( "bcc" ) ){ + setProperty( "bcc","" ); + } + if( NOT propertyExists( "mailport" ) ){ + setProperty( "mailport",25); + } + if( NOT propertyExists( "mailserver" ) ){ + setProperty( "mailserver","" ); + } + if( NOT propertyExists( "mailpassword" ) ){ + setProperty( "mailpassword","" ); + } + if( NOT propertyExists( "mailusername" ) ){ + setProperty( "mailusername","" ); + } + if( NOT propertyExists( "useTLS" ) ){ + setProperty( "useTLS","false" ); + } + if( NOT propertyExists( "useSSL" ) ){ + setProperty( "useSSL","false" ); + } + + return this; + } + + /** + * Write an entry into the appender. You must implement this method yourself. + * + * @logEvent The logging event to log + */ + function logMessage( required wirebox.system.logging.LogEvent logEvent ){ + var loge = arguments.logEvent; + var subject = "#severityToString( loge.getSeverity() )#-#loge.getCategory()#-#getProperty( "subject" )#"; + var entry = ""; + + try{ + if( hasCustomLayout() ){ + entry = getCustomLayout().format( loge ); + if( structKeyExists( getCustomLayout(), "getSubject" ) ){ + subject = getCustomLayout().getSubject( loge ); + } + } else { + savecontent variable="entry"{ + writeOutput( " +

TimeStamp: #loge.getTimeStamp()#

+

Severity: #loge.getSeverity()#

+

Category: #loge.getCategory()#

+
+

#loge.getMessage()#

+
+

Extra Info Dump:

+ " ); + writeDump( loge.getExtraInfo() ); + } + } + + if( len( getProperty( "mailserver" ) ) ){ + cfmail( + to = getProperty( "to" ), + from = getProperty( "from" ), + cc = getProperty( "cc" ), + bcc = getProperty( "bcc" ), + type = "text/html", + useTLS = getProperty( "useTLS" ), + useSSL = getProperty( "useSSL" ), + server = getProperty( "mailserver" ), + port = getProperty( "mailport" ), + username = getProperty( "mailusername" ), + password = getProperty( "mailpassword" ), + subject = subject + ){ + writeOutput( entry ); + } + } else { + cfmail( + to = getProperty( "to" ), + from = getProperty( "from" ), + cc = getProperty( "cc" ), + bcc = getProperty( "bcc" ), + type = "text/html", + useTLS = getProperty( "useTLS" ), + useSSL = getProperty( "useSSL" ), + subject = subject + ){ + writeOutput( entry ); + } + } + + } catch( Any e ){ + $log( + "ERROR", + "Error sending email from appender #getName()#. #e.message# #e.detail# #e.stacktrace#" + ); + } + + return this; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/logging/appenders/FileAppender.cfc b/src/cfml/system/_wirebox/system/logging/appenders/FileAppender.cfc new file mode 100644 index 000000000..88a144477 --- /dev/null +++ b/src/cfml/system/_wirebox/system/logging/appenders/FileAppender.cfc @@ -0,0 +1,337 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * An appender that leverages the OS file system + * + * Properties: + * - filepath : The location of where to store the log file. + * - autoExpand : Whether to expand the file path or not. Defaults to true. + * - filename : The name of the file, if not defined, then it will use the name of this appender. Do not append an extension to it. We will append a .log to it. + * - fileEncoding : The file encoding to use, by default we use ISO-8859-1; +**/ +component accessors="true" extends="wirebox.system.logging.AbstractAppender"{ + + /** + * The log file location + */ + property name="logFullpath"; + + /** + * The default lock name + */ + property name="lockName"; + + /** + * The default lock timeout + */ + property name="lockTimeout" default="25" type="numeric"; + + /** + * Log Listener Queue + */ + property name="logListener" type="struct"; + + /** + * Constructor + * + * @name The unique name for this appender. + * @properties A map of configuration properties for the appender" + * @layout The layout class to use in this appender for custom message rendering. + * @levelMin The default log level for this appender, by default it is 0. Optional. ex: LogBox.logLevels.WARN + * @levelMax The default log level for this appender, by default it is 5. Optional. ex: LogBox.logLevels.WARN + * + * @throws FileAppender.PropertyNotFound + */ + function init( + required name, + struct properties={}, + layout="", + levelMin=0, + levelMax=4 + ){ + super.init( argumentCollection=arguments ); + + // Setup Properties + if( NOT propertyExists( "filepath" ) ){ + throw( + message = "Filepath property not defined", + type = "FileAppender.PropertyNotFound" + ); + } + if( NOT propertyExists( "autoExpand" ) ){ + setProperty( "autoExpand", true ); + } + if( NOT propertyExists( "filename" ) ){ + setProperty( "filename", getName() ); + } + if( NOT propertyExists( "fileEncoding" ) ){ + setProperty( "fileEncoding", "ISO-8859-1" ); + } + // Cleanup File Names + setProperty( "filename", REreplacenocase( getProperty( "filename" ), "[^0-9a-z]", "", "ALL" ) ); + + // Setup the log file full path + variables.logFullpath = getProperty( "filePath" ); + // Clean ending slash + variables.logFullPath = reReplacenocase( variables.logFullPath, "[/\\]$", "" ); + // Concatenate Full Log path + variables.logFullPath = variables.logFullpath & "/" & getProperty( "filename" ) & ".log"; + + // Do we expand the path? + if( getProperty( "autoExpand" ) ){ + variables.logFullPath = expandPath( variables.logFullpath ); + } + + // lock information + variables.lockName = getHash() & getname() & "logOperation"; + variables.lockTimeout = 25; + + // Activate Log Listener Queue + variables.logListener = { + active = false, + queue = [] + }; + + // Declare locking construct + variables.lock = function( type="exclusive", body ){ + lock name="#getHash() & getName()#-logListener" + type=arguments.type + timeout="#variables.lockTimeout#" + throwOnTimeout=true{ + + return arguments.body(); + + } + }; + + return this; + } + + /** + * Write an entry into the appender. You must implement this method yourself. + * + * @logEvent The logging event to log + */ + function logMessage( required wirebox.system.logging.LogEvent logEvent ){ + var loge = arguments.logEvent; + var timestamp = loge.getTimestamp(); + var message = loge.getMessage(); + var entry = ""; + + + // Message Layout + if( hasCustomLayout() ){ + entry = getCustomLayout().format( loge ); + } else { + // Cleanup main message + if( len( loge.getExtraInfoAsString() ) ){ + message = message & " " & loge.getExtraInfoAsString(); + } + message = replace( message, '"', '""', "all" ); + message = replace( message, "#chr(13)##chr(10)#", ' ', "all" ); + 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#"'; + } + + // Log it + append( entry ); + + return this; + } + + /** + * Called upon registration + */ + FileAppender function onRegistration(){ + // Init the log location + initLogLocation(); + + return this; + } + + /** + * Remove the log file for this appender + */ + FileAppender function removeLogFile(){ + if( fileExists( variables.logFullPath ) ){ + + variables.lock( body=function(){ + if( fileExists( variables.logFullPath ) ){ + fileDelete( variables.logFullPath ); + } // end double lock race condition + } ); + + } // end if + + return this; + } + + /** + * Initialize the file log location if it does not exist. Please note that if exceptions are detected, then we log them in the CF facilities + */ + FileAppender function initLogLocation(){ + if( !fileExists( variables.logFullPath ) ){ + + variables.lock( body=function(){ + if( !fileExists( variables.logFullPath ) ){ + try{ + // Default Log Directory + ensureDefaultLogDirectory(); + // Create log file + append( '"Severity","Appender","Date","Time","Category","Message"' ); + } catch( Any e ) { + $log( "ERROR", "Cannot create appender's: #getName()# log file. File #variables.logFullpath#. #e.message# #e.detail#" ); + } + } // end double lock race condition + } ); + + } + + return this; + } + + /** + * Start the log listener so we can queue up the logging to alleviate for disk operations + */ + function startLogListener(){ + + // Verify if listener has started. + var isActive = variables.lock( "readonly", function(){ + return variables.logListener.active; + } ); + + if( isActive ){ + //out( "Listener already active exiting startup..." ); + return; + } else { + //out( "Listener needs to startup" ); + } + + // Check if we are in a thread already, if so, just skip + if( getUtil().inThread() ){ + return; + } + + thread action="run" name="#variables.lockName#-#hash( createUUID() )#"{ + // Activate listener + var isActivating = variables.lock( body=function(){ + if( !variables.logListener.active ){ + //out( "listener #getHash()# min: #getLevelMin()# max: #getLevelMax()# marked as active" ); + variables.logListener.active = true; + return true; + } else { + //out( "listener was just marked as active, just existing lock" ); + return false; + } + } ); + + if( !isActivating ){ return; } + + var lastRun = getTickCount(); + var start = lastRun; + var maxIdle = 15000; // 15 seconds is how long the threads can live for. + var flushInterval = 1000; // 1 second + var sleepInterval = 50; + var count = 0; + + // Ensure Log File + initLogLocation(); + + var oFile = fileOpen( variables.logFullPath, "append", this.getProperty( "fileEncoding" ) ); + var hasMessages = false; + + try{ + //out( "Starting #getName()# thread", true ); + + // Execute only if there are messages in the queue or the internal has been crossed + while( + variables.logListener.queue.len() || lastRun + maxIdle > getTickCount() + ){ + + //out( "len: #variables.logListener.queue.len()# last run: #lastRun# idle: #maxIdle#" ); + + if( variables.logListener.queue.len() ){ + // pop and dequeue + var thisMessage = variables.logListener.queue[ 1 ]; + variables.logListener.queue.deleteAt( 1 ); + + if( isSimpleValue( oFile ) ){ + oFile = fileOpen( variables.logFullPath, "append", this.getProperty( "fileEncoding" ) ); + } + + //out( "Wrote to file #thisMessage#" ); + + // Write to file + fileWriteLine( oFile, thisMessage ); + + // Mark the last run + lastRun = getTickCount(); + } + + // flush to disk every start + 1000ms + if( start + flushInterval < getTickCount() && !isSimpleValue( oFile ) ){ + //out( "LogFile for #getName()# flushed at #start# + #flushInterval#", true ); + fileClose( oFile ); + oFile = ""; + start = getTickCount(); + } + + //out( "Sleeping: lastRun #lastRun + maxIdle#" ); + + sleep( sleepInterval ); // take a nap + } + + } catch( Any e ){ + $log( "ERROR", "Error processing log listener: #e.message# #e.detail# #e.stacktrace#" ); + //out( "Error with listener thread for #getName()#" & e.message & e.detail ); + } finally { + //out( "Stopping listener thread for #getName()#, we have done our job" ); + + // Stop log listener + variables.lock( body=function(){ + variables.logListener.active = false; + } ); + + if( !isSimpleValue( oFile ) ){ + fileClose( oFile ); + oFile = ""; + } + } + + } // end threading + } + + /************************************ PRIVATE ************************************/ + + /** + * Append a message to the log file + * + * @message The target message + */ + private FileAppender function append( required message ){ + // Ensure log listener + startLogListener(); + + // queue message up + arrayAppend( variables.logListener.queue, arguments.message ); + + return this; + } + + /** + * Ensures the log directory. + */ + private function ensureDefaultLogDirectory(){ + var dirPath = getDirectoryFrompath( variables.logFullpath ); + + if( !directoryExists( dirPath ) ){ + directoryCreate( dirPath ); + } + + return this; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/logging/appenders/RollingFileAppender.cfc b/src/cfml/system/_wirebox/system/logging/appenders/RollingFileAppender.cfc new file mode 100644 index 000000000..094508f5e --- /dev/null +++ b/src/cfml/system/_wirebox/system/logging/appenders/RollingFileAppender.cfc @@ -0,0 +1,81 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * A RollingFileAppender. This appenders rotates the log files according to the properties defined. + * + * Properties: + * + * - filepath : The location of where to store the log file. + * - autoExpand : Whether to expand the file path or not. Defaults to true. + * - filename : The name of the file, if not defined, then it will use the name of this appender. Do not append an extension to it. We will append a .log to it. + * - fileEncoding : The file encoding to use, by default we use UTF-8; + * - fileMaxSize : The max file size for log files. Defaults to 2000 (2 MB) + * - fileMaxArchives : The max number of archives to keep. Defaults to 2. +**/ +component accessors="true" extends="wirebox.system.logging.appenders.FileAppender" { + + /** + * Constructor + * + * @name The unique name for this appender. + * @properties A map of configuration properties for the appender" + * @layout The layout class to use in this appender for custom message rendering. + * @levelMin The default log level for this appender, by default it is 0. Optional. ex: LogBox.logLevels.WARN + * @levelMax The default log level for this appender, by default it is 5. Optional. ex: LogBox.logLevels.WARN + */ + function init( + required name, + struct properties={}, + layout="", + levelMin=0, + levelMax=4 + ){ + super.init( argumentCollection=arguments ); + + if( NOT propertyExists( "fileMaxSize" ) OR NOT isNumeric( getProperty( "fileMaxSize" ) ) ){ + setProperty( "fileMaxSize", "2000" ); + } + if( NOT propertyExists( "fileMaxArchives" ) OR NOT isNumeric( getProperty( "fileMaxArchives" ) ) ){ + setProperty( "fileMaxArchives", "2" ); + } + + variables.fileRotator = new wirebox.system.logging.util.FileRotator(); + + return this; + } + + /** + * Write an entry into the appender. You must implement this method yourself. + * + * @logEvent The logging event to log + */ + function logMessage( required wirebox.system.logging.LogEvent logEvent ){ + // Log the message in the super class + super.logMessage( arguments.logEvent ); + + // Rotate + try{ + // Verify if listener has started. + var isActive = variables.lock( "readonly", function(){ + return variables.logListener.active; + } ); + + // Only process rotation if the log listener is disabled + if( !isActive ){ + // Lock so we can do rotation + variables.lock( body=function(){ + variables.fileRotator.checkRotation( this ); + } ); + } + } catch( Any e ) { + $log( + "ERROR", + "Could not zip and rotate log files in #getName()#. #e.message# #e.detail#" + ); + } + + return this; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/logging/appenders/ScopeAppender.cfc b/src/cfml/system/_wirebox/system/logging/appenders/ScopeAppender.cfc new file mode 100644 index 000000000..4b8ca7da4 --- /dev/null +++ b/src/cfml/system/_wirebox/system/logging/appenders/ScopeAppender.cfc @@ -0,0 +1,123 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * A simple Scope Appender that logs to a specified scope. + * Properties: + * - scope : the scope to persist to, defaults to request (optional) + * - key : the key to use in the scope, it defaults to the name of the Appender (optional) + * - limit : a limit to the amount of logs to rotate. Defaults to 0, unlimited (optional) +**/ +component accessors="true" extends="wirebox.system.logging.AbstractAppender"{ + + /** + * Constructor + * + * @name The unique name for this appender. + * @properties A map of configuration properties for the appender" + * @layout The layout class to use in this appender for custom message rendering. + * @levelMin The default log level for this appender, by default it is 0. Optional. ex: LogBox.logLevels.WARN + * @levelMax The default log level for this appender, by default it is 5. Optional. ex: LogBox.logLevels.WARN + */ + function init( + required name, + struct properties={}, + layout="", + levelMin=0, + levelMax=4 + ){ + // Init supertype + super.init( argumentCollection=arguments ); + + // Verify properties + if( NOT propertyExists( "scope" ) ){ + setProperty( "scope", "request" ); + } + if( NOT propertyExists( "key" ) ){ + setProperty( "key", getName() ); + } + if( NOT propertyExists( "limit" ) OR NOT isNumeric( getProperty( "limit" ) ) ){ + setProperty( "limit", 0 ); + } + + // Scope storage + variables.scopeStorage = new wirebox.system.core.collections.ScopeStorage(); + // Scope Checks + variables.scopeStorage.scopeCheck( getproperty( "scope" ) ); + // UUID generator + variables.uuid = createobject( "java", "java.util.UUID" ); + + return this; + } + + /** + * Write an entry into the appender. You must implement this method yourself. + * + * @logEvent The logging event to log + */ + function logMessage( required wirebox.system.logging.LogEvent logEvent ){ + var entry = structnew(); + var limit = getProperty( "limit" ); + var loge = arguments.logEvent; + + // Verify storage + ensureStorage(); + + // Check Limits + var logStack = getStorage(); + + if( limit GT 0 and arrayLen( logStack ) GTE limit ){ + // pop one out, the oldest + arrayDeleteAt( logStack, 1 ); + } + + // Log Away + entry.id = variables.uuid.randomUUID().toString(); + entry.logDate = loge.getTimeStamp(); + entry.appenderName = getName(); + entry.severity = severityToString( loge.getseverity() ); + entry.message = loge.getMessage(); + entry.extraInfo = loge.getextraInfo(); + entry.category = loge.getCategory(); + + // Save Storage + arrayAppend( logStack, entry ); + saveStorage( logStack ); + + return this; + } + + /************************************ PRIVATE ***************************************/ + + /** + * Get the storage + */ + private any function getStorage(){ + lock name="#getname()#.scopeoperation" type="exclusive" timeout="20" throwOnTimeout="true"{ + return variables.scopeStorage.get( getProperty( "key" ), getProperty( "scope" ) ); + } + } + + /** + * Save Storage + * + * @data The data to store + */ + private function saveStorage( required data ){ + lock name="#getname()#.scopeoperation" type="exclusive" timeout="20" throwOnTimeout="true"{ + variables.scopeStorage.put( getProperty( "key" ), arguments.data, getProperty( "scope" ) ); + } + return this; + } + + /** + * Ensure the first storage in the scope + */ + private function ensureStorage(){ + if( NOT variables.scopeStorage.exists( getProperty( "key" ), getproperty( "scope" ) ) ){ + variables.scopeStorage.put( getProperty( "key" ), [], getProperty( "scope" ) ); + } + return this; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/logging/appenders/SocketAppender.cfc b/src/cfml/system/_wirebox/system/logging/appenders/SocketAppender.cfc new file mode 100644 index 000000000..bbfd780ce --- /dev/null +++ b/src/cfml/system/_wirebox/system/logging/appenders/SocketAppender.cfc @@ -0,0 +1,162 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * A socket appender that logs to a socket + * + * Properties: + * - host : the host to connect to + * - port : the port to connect to + * - timeout : the timeout in seconds. defaults to 5 seconds + * - persistConnection : Whether to persist the connection or create a new one every log time. Defaults to true; +**/ +component accessors="true" extends="wirebox.system.logging.AbstractAppender" { + + /** + * The actual socket server + */ + property name="socket"; + + /** + * The socket writer class + */ + property name="socketWriter"; + + /** + * Constructor + * + * @name The unique name for this appender. + * @properties A map of configuration properties for the appender" + * @layout The layout class to use in this appender for custom message rendering. + * @levelMin The default log level for this appender, by default it is 0. Optional. ex: LogBox.logLevels.WARN + * @levelMax The default log level for this appender, by default it is 5. Optional. ex: LogBox.logLevels.WARN + * + * @throws SocketAppender.HostNotFound,SocketAppender.PortNotFound + */ + function init( + required name, + struct properties={}, + layout="", + levelMin=0, + levelMax=4 + ){ + // Init supertype + super.init( argumentCollection=arguments ); + + // Verify properties + if( NOT propertyExists( "host" ) ){ + throw( message="The host must be provided", type="SocketAppender.HostNotFound" ); + } + if( NOT propertyExists( "port" ) ){ + throw( message="The port must be provided", type="SocketAppender.PortNotFound" ); + } + if( NOT propertyExists( "timeout" ) OR NOT isNumeric( getProperty( "timeout" ) ) ){ + setProperty( "timeout", 5); + } + if( NOT propertyExists( "persistConnection" ) ){ + setProperty( "persistConnection", true ); + } + + // Socket storage + variables.socket = ""; + variables.socketWriter = ""; + + return this; + } + + /** + * Write an entry into the appender. You must implement this method yourself. + * + * @logEvent The logging event to log + */ + function logMessage( required wirebox.system.logging.LogEvent logEvent ){ + var loge = arguments.logEvent; + var entry = ""; + + // Prepare entry to send. + if( hasCustomLayout() ){ + entry = getCustomLayout().format( loge ); + } + else{ + entry = "#severityToString( loge.getseverity() )# #loge.getCategory()# #loge.getmessage()# ExtraInfo: #loge.getextraInfoAsString()#"; + } + + // Open connection? + if( NOT getProperty( "persistConnection" ) ){ + openConnection(); + } + + // Send data to Socket + try{ + getSocketWriter().println(entry); + } catch( Any e ) { + $log( + "ERROR", + "#getName()# - Error sending entry to socket #getProperties().toString()#. #e.message# #e.detail#" + ); + } + + // Close Connection? + if( NOT getProperty( "persistConnection" ) ){ + closeConnection(); + } + + return this; + } + + /** + * When registration occurs + */ + function onRegistration(){ + if ( getProperty( "persistConnection" ) ) { + openConnection(); + } + return this; + } + + /** + * When Unregistration occurs + */ + function onUnRegistration(){ + if ( getProperty( "persistConnection" ) ) { + closeConnection(); + } + return this; + } + + /****************************************** PRIVATE ***************************************/ + + /** + * Open a socket connection + * + * @throws SocketAppender.ConnectionException + */ + private function openConnection(){ + try{ + variables.socket = createObject( "java", "java.net.Socket" ).init( getProperty( "host" ), javaCast( "int", getProperty( "port" ) ) ); + } catch( Any e ) { + throw( + message = "Error opening socket to #getProperty( "host" )#:#getProperty( "port" )#", + detail = e.message & e.detail & e.stacktrace, + type = "SocketAppender.ConnectionException" + ); + } + // Set Timeout + variables.socket.setSoTimeout( javaCast( "int", getProperty( "timeout" ) * 1000 ) ); + + //Prepare Writer + variables.socketWriter = createObject( "java","java.io.PrintWriter" ).init( variables.socket.getOutputStream() ); + + return this; + } + + /** + * Close Connection + */ + function closeConnection(){ + getSocketWriter().close(); + getSocket().close(); + return this; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/logging/appenders/TracerAppender.cfc b/src/cfml/system/_wirebox/system/logging/appenders/TracerAppender.cfc new file mode 100644 index 000000000..80bf34a41 --- /dev/null +++ b/src/cfml/system/_wirebox/system/logging/appenders/TracerAppender.cfc @@ -0,0 +1,59 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * A simple cftracer appender +**/ +component accessors="true" extends="wirebox.system.logging.AbstractAppender" { + + /** + * Constructor + * + * @name The unique name for this appender. + * @properties A map of configuration properties for the appender" + * @layout The layout class to use in this appender for custom message rendering. + * @levelMin The default log level for this appender, by default it is 0. Optional. ex: LogBox.logLevels.WARN + * @levelMax The default log level for this appender, by default it is 5. Optional. ex: LogBox.logLevels.WARN + */ + function init( + required name, + struct properties={}, + layout="", + levelMin=0, + levelMax=4 + ){ + // Init supertype + super.init( argumentCollection=arguments ); + + return this; + } + + /** + * Write an entry into the appender. You must implement this method yourself. + * + * @logEvent The logging event to log + */ + function logMessage( required wirebox.system.logging.LogEvent logEvent ){ + var loge = arguments.logEvent; + var entry = ""; + var traceSeverity = "information"; + + if( hasCustomLayout() ){ + entry = getCustomLayout().format( loge ); + } else { + entry = "#loge.getMessage()# ExtraInfo: #loge.getextraInfoAsString()#"; + } + + // Severity by cftrace + switch( this.logLevels.lookupCF( loge.getSeverity() ) ){ + case "FATAL" : { traceSeverity = "fatal information"; break; } + case "ERROR" : { traceSeverity = "error"; break; } + case "WARN" : { traceSeverity = "warning"; break; } + } + + cftrace( category="#loge.getCategory()#", text="#entry#", type="#traceSeverity#" ); + + return this; + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/logging/config/LogBoxConfig.cfc b/src/cfml/system/_wirebox/system/logging/config/LogBoxConfig.cfc new file mode 100644 index 000000000..bd8d6ef12 --- /dev/null +++ b/src/cfml/system/_wirebox/system/logging/config/LogBoxConfig.cfc @@ -0,0 +1,421 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* This is a LogBox configuration object. You can use it to configure a LogBox instance. +**/ +component accessors="true"{ + + // The log levels enum as a public property + this.logLevels = new wirebox.system.logging.LogLevels(); + // Internal Utility object + variables.utility = new wirebox.system.core.util.Util(); + // Instance private scope + instance = structnew(); + // Startup the configuration + reset(); + + /** + * Constructor + * + * @CFCConfig The logBox Data Configuration CFC + * @CFCConfigPath The logBox Data Configuration CFC path to use + */ + function init( any CFCConfig, string CFCConfigPath ){ + // Test and load via Data CFC Path + if( structKeyExists( arguments, "CFCConfigPath" ) ){ + arguments.CFCConfig = createObject( "component", arguments.CFCConfigPath ); + } + + // Test and load via Data CFC + if( structKeyExists( arguments, "CFCConfig") and isObject( arguments.CFCConfig ) ){ + // Decorate our data CFC + arguments.CFCConfig.getPropertyMixin = variables.utility.getMixerUtil().getPropertyMixin; + // Execute the configuration + arguments.CFCConfig.configure(); + // Get Data + var logBoxDSL = arguments.CFCConfig.getPropertyMixin( "logBox", "variables", structnew() ); + // Load the DSL + loadDataDSL( logBoxDSL ); + } + + // Just return, most likely programmatic config + return this; + } + + /** + * Reset the configuration + */ + LogBoxConfig function reset(){ + // Register appenders + instance.appenders = structnew(); + // Register categories + instance.categories = structnew(); + // Register root logger + instance.rootLogger = structnew(); + return this; + } + + /** + * Load a data configuration CFC data DSL + * + * @rawDSL The data configuration DSL structure + */ + LogBoxConfig function loadDataDSL( required struct rawDSL ){ + var logBoxDSL = arguments.rawDSL; + + // Register Appenders + for( var key in logBoxDSL.appenders ){ + logBoxDSL.appenders[ key ].name = key; + appender( argumentCollection=logBoxDSL.appenders[ key ] ); + } + + // Register Root Logger + if( NOT structKeyExists( logBoxDSL, "root" ) ){ + logBoxDSL.root = { appenders = "*" }; + } + root( argumentCollection=logBoxDSL.root ); + + // Register Categories + if( structKeyExists( logBoxDSL, "categories") ){ + for( var key in logBoxDSL.categories ){ + logBoxDSL.categories[ key ].name = key; + category( argumentCollection=logBoxDSL.categories[ key ] ); + } + } + + // Register Level Categories + if( structKeyExists( logBoxDSL, "debug" ) ){ + DEBUG( argumentCollection=variables.utility.arrayToStruct( logBoxDSL.debug ) ); + } + if( structKeyExists( logBoxDSL, "info" ) ){ + INFO( argumentCollection=variables.utility.arrayToStruct( logBoxDSL.info ) ); + } + if( structKeyExists( logBoxDSL, "warn" ) ){ + WARN( argumentCollection=variables.utility.arrayToStruct( logBoxDSL.warn ) ); + } + if( structKeyExists( logBoxDSL, "error" ) ){ + ERROR( argumentCollection=variables.utility.arrayToStruct( logBoxDSL.error ) ); + } + if( structKeyExists( logBoxDSL, "fatal" ) ){ + FATAL( argumentCollection=variables.utility.arrayToStruct( logBoxDSL.fatal ) ); + } + if( structKeyExists( logBoxDSL, "off" ) ){ + OFF( argumentCollection=variables.utility.arrayToStruct( logBoxDSL.off ) ); + } + + return this; + } + + /** + * Reset appender configuration + */ + LogBoxConfig function resetAppenders(){ + instance.appenders = structNew(); + return this; + } + + /** + * Reset categories configuration + */ + LogBoxConfig function resetCategories(){ + instance.categories = structNew(); + return this; + } + + /** + * Reset root configuration + */ + LogBoxConfig function resetRoot(){ + instance.rootLogger = structNew(); + return this; + } + + /** + * Get the instance memento + */ + struct function getMemento(){ + return instance; + } + + /** + * Validates the configuration. If not valid, it will throw an appropriate exception. + * + * @throws AppenderNotFound + */ + LogBoxConfig function validate(){ + // Check root logger definition + if( structIsEmpty( instance.rootLogger ) ){ + // Auto register a root logger + root( appenders="*" ); + } + + // All root appenders? + if( instance.rootLogger.appenders eq "*" ){ + instance.rootLogger.appenders = structKeyList( getAllAppenders() ); + } + + // Check root's appenders + for( var x=1; x lte listlen( instance.rootLogger.appenders ); x++ ){ + if( NOT structKeyExists( instance.appenders, listGetAt( instance.rootLogger.appenders, x ) ) ){ + throw( + message = "Invalid appender in Root Logger", + detail = "The appender #listGetAt( instance.rootLogger.appenders, x )# has not been defined yet. Please define it first.", + type = "AppenderNotFound" + ); + } + } + + // Check all Category Appenders + for( var key in instance.categories ){ + + // Check * all appenders + if( instance.categories[ key ].appenders eq "*" ){ + instance.categories[ key ].appenders = structKeyList( getAllAppenders() ); + } + + for( var x=1; x lte listlen( instance.categories[ key ].appenders ); x++ ){ + if( NOT structKeyExists( instance.appenders, listGetAt( instance.categories[ key ].appenders, x ) ) ){ + throw( + message = "Invalid appender in Category: #key#", + detail = "The appender #listGetAt(instance.categories[key].appenders,x)# has not been defined yet. Please define it first.", + type = "AppenderNotFound" + ); + } + } + } + + return this; + } + + /** + * Add an appender configuration + * + * @name A unique name for the appender to register. Only unique names can be registered per instance + * @class The appender's class to register. We will create, init it and register it for you + * @properties The structure of properties to configure this appender with. + * @layout The layout class path to use in this appender for custom message rendering. + * @levelMin The default log level for the root logger, by default it is 0 (FATAL). Optional. ex: config.logLevels.WARN + * @levelMax The default log level for the root logger, by default it is 4 (DEBUG). Optional. ex: config.logLevels.WARN + */ + LogBoxConfig function appender( + required name, + required class, + struct properties={}, + layout="", + levelMin=0, + levelMax=4 + ){ + // Convert Levels + convertLevels( arguments ); + + // Check levels + levelChecks( arguments.levelMin, arguments.levelMax ); + + // Register appender + instance.appenders[ arguments.name ] = arguments; + + return this; + } + + /** + * Add an appender configuration + * + * @appenders A list of appenders to configure the root logger with. Send a * to add all appenders + * @levelMin The default log level for the root logger, by default it is 0 (FATAL). Optional. ex: config.logLevels.WARN + * @levelMax The default log level for the root logger, by default it is 4 (DEBUG). Optional. ex: config.logLevels.WARN + * + * @throws InvalidAppenders + */ + LogBoxConfig function root( + required appenders, + levelMin=0, + levelMax=4 + ){ + // Convert Levels + convertLevels( arguments ); + + // Check levels + levelChecks( arguments.levelMin, arguments.levelMax ); + + //Verify appender list + if( NOT listLen(arguments.appenders) ){ + throw( + message = "Invalid Appenders", + detail = "Please send in at least one appender for the root logger", + type = "InvalidAppenders" + ); + } + + // Add definition + instance.rootLogger = arguments; + + return this; + } + + /** + * Get the root logger definition + */ + struct function getRoot(){ + return instance.rootLogger; + } + + /** + * Add a new category configuration with appender(s). Appenders MUST be defined first, else this method will throw an exception + * + * @name A unique name for the appender to register. Only unique names can be registered per instance + * @levelMin The default log level for the root logger, by default it is 0 (FATAL). Optional. ex: config.logLevels.WARN + * @levelMax The default log level for the root logger, by default it is 4 (DEBUG). Optional. ex: config.logLevels.WARN + * @appenders A list of appender names to configure this category with. By default it uses all the registered appenders + */ + LogBoxConfig function category( + required name, + levelMin=0, + levelMax=4, + appenders="*" + ){ + // Convert Levels + convertLevels( arguments ); + + // Check levels + levelChecks( arguments.levelMin, arguments.levelMax ); + + // Add category registration + instance.categories[ arguments.name ] = arguments; + + return this; + } + + /** + * Get a specified category definition + * + * @name The category name + */ + struct function getCategory( required name ){ + return instance.categories[ arguments.name ]; + } + + /** + * Check if a category definition exists + * + * @name The category name + */ + boolean function categoryExists( required name ){ + return structKeyExists( instance.categories, arguments.name ); + } + + /** + * Get the configured categories + */ + struct function getAllCategories(){ + return instance.categories; + } + + /** + * Get all the configured appenders + */ + struct function getAllAppenders(){ + return instance.appenders; + } + + /** + * Add categories to the DEBUG level. Send each category as an argument. + */ + LogBoxConfig function debug(){ + for( var key in arguments ){ + category( name=arguments[ key ], levelMax=this.logLevels.DEBUG ); + } + return this; + } + + /** + * Add categories to the INFO level. Send each category as an argument. + */ + LogBoxConfig function info(){ + for( var key in arguments ){ + category( name=arguments[ key ], levelMax=this.logLevels.INFO ); + } + return this; + } + + /** + * Add categories to the WARN level. Send each category as an argument. + */ + LogBoxConfig function warn(){ + for( var key in arguments ){ + category( name=arguments[ key ], levelMax=this.logLevels.WARN ); + } + return this; + } + + /** + * Add categories to the ERROR level. Send each category as an argument. + */ + LogBoxConfig function error(){ + for( var key in arguments ){ + category( name=arguments[ key ], levelMax=this.logLevels.ERROR ); + } + return this; + } + + /** + * Add categories to the FATAL level. Send each category as an argument. + */ + LogBoxConfig function fatal(){ + for( var key in arguments ){ + category( name=arguments[ key ], levelMax=this.logLevels.FATAL ); + } + return this; + } + + /** + * Add categories to the OFF level. Send each category as an argument. + */ + LogBoxConfig function off(){ + for( var key in arguments ){ + category( name=arguments[ key ], levelMin=this.logLevels.OFF, levelMax=this.logLevels.OFF ); + } + return this; + } + + /** + * Convert levels from an incoming structure of data + * + * @target The structure to look for elements: LevelMin and LevelMax + */ + private struct function convertLevels( required target ){ + // Check levelMin + if( structKeyExists( arguments.target, "levelMIN" ) and NOT isNumeric( arguments.target.levelMin ) ){ + arguments.target.levelMin = this.logLevels.lookupAsInt(arguments.target.levelMin); + } + // Check levelMax + if( structKeyExists( arguments.target, "levelMax" ) and NOT isNumeric( arguments.target.levelMax ) ){ + arguments.target.levelMax = this.logLevels.lookupAsInt( arguments.target.levelMax ); + } + // For chaining + return arguments.target; + } + + /** + * Level checks on incoming levels + * + * @levelMin + * @levelMax + * + * @throws InvalidLevel + */ + private function levelChecks( required levelMin, required levelMax ){ + if ( !this.logLevels.isLevelValid( arguments.levelMin ) ) { + throw( + message = "LevelMin #arguments.levelMin# is not a valid level.", + type = "InvalidLevel" + ); + } else if ( !this.logLevels.isLevelValid( arguments.levelMax ) ) { + throw( + message = "LevelMin #arguments.levelMax# is not a valid level.", + type = "InvalidLevel" + ); + } + } + +} \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/logging/config/samples/Sample.LogBox.cfc b/src/cfml/system/_wirebox/system/logging/config/samples/Sample.LogBox.cfc new file mode 100644 index 000000000..712ffc846 --- /dev/null +++ b/src/cfml/system/_wirebox/system/logging/config/samples/Sample.LogBox.cfc @@ -0,0 +1,41 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* The default LogBox configuration object +**/ +component{ + + /** + * Configure LogBox, that's it! + */ + function configure(){ + logBox = { + // Define Appenders + appenders = { + coldboxTracer = { + class="wirebox.system.logging.appenders.ConsoleAppender", + layout="coldbox.testing.cases.logging.MockLayout", + properties = { + name = "awesome" + } + } + }, + // Root Logger + root = { levelmax="INFO", levelMin=0, appenders="*" }, + // Categories + categories = { + "coldbox.system" = { levelMax="INFO" }, + "wirebox.system.interceptors" = { levelMin=0, levelMax="DEBUG", appenders="*" }, + "hello.model" = {levelMax=4, appenders="*" } + }, + debug = [ "coldbox.system", "model.system" ], + info = [ "hello.model", "yes.wow.wow" ], + warn = [ "hello.model", "yes.wow.wow" ], + error = [ "hello.model", "yes.wow.wow" ], + fatal = [ "hello.model", "yes.wow.wow" ], + OFF = [ "hello.model", "yes.wow.wow" ] + }; + } + +} diff --git a/src/cfml/system/_wirebox/system/logging/license.txt b/src/cfml/system/_wirebox/system/logging/license.txt new file mode 100644 index 000000000..3de5291cb --- /dev/null +++ b/src/cfml/system/_wirebox/system/logging/license.txt @@ -0,0 +1,22 @@ +******************************************************************************** +Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +www.ortussolutions.com +******************************************************************************** +ColdBox is open source. However, if you use this product please know that it is bound to the following Licence. +If you use ColdBox, please make mention of it in your code or web site or add a Powered By Coldbox icon. + +Apache License, Version 2.0 + +Copyright [2007] [Luis Majano and Ortus Solutions,Corp] + +Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/src/cfml/system/_wirebox/system/logging/readme.md b/src/cfml/system/_wirebox/system/logging/readme.md new file mode 100644 index 000000000..8b812849c --- /dev/null +++ b/src/cfml/system/_wirebox/system/logging/readme.md @@ -0,0 +1,100 @@ +``` + _ ____ + | | | _ \ + | | ___ __ _| |_) | _____ __ + | | / _ \ / _` | _ < / _ \ \/ / + | |___| (_) | (_| | |_) | (_) > < + |______\___/ \__, |____/ \___/_/\_\ + __/ | + |___/ +``` + +Copyright Since 2005 ColdBox Platform by Luis Majano and Ortus Solutions, Corp +www.coldbox.org | www.ortussolutions.com + +---- + +Because of God's grace, this project exists. If you don't like this, then don't read it, its not for you. + +>"Therefore being justified by faith, we have peace with God through our Lord Jesus Christ: +By whom also we have access by faith into this grace wherein we stand, and rejoice in hope of the glory of God. +And not only so, but we glory in tribulations also: knowing that tribulation worketh patience; +And patience, experience; and experience, hope: +And hope maketh not ashamed; because the love of God is shed abroad in our hearts by the +Holy Ghost which is given unto us. ." Romans 5:5 + +---- + +# Welcome to LogBox +LogBox is an enterprise ColdFusion (CFML) logging library designed to give you flexibility, simplicity and power when logging or tracing is needed in your applications. + +## License +Apache License, Version 2.0. + +>The ColdBox Websites, logo and content have a separate license and they are a separate entity. + +## Versioning +LogBox is maintained under the Semantic Versioning guidelines as much as possible. + +Releases will be numbered with the following format: + +``` +.. +``` + +And constructed with the following guidelines: + +* Breaking backward compatibility bumps the major (and resets the minor and patch) +* New additions without breaking backward compatibility bumps the minor (and resets the patch) +* Bug fixes and misc changes bumps the patch + +## Important Links + +Source Code +- https://github.com/coldbox/coldbox-platform + +Continuous Integration +- https://travis-ci.org/ColdBox/coldbox-platform + +Bug Tracking/Agile Boards +- https://ortussolutions.atlassian.net/browse/LOGBOX + +Documentation +- https://logbox.ortusbooks.com + +Blog +- https://www.ortussolutions.com/blog + +Official Site +- https://www.coldbox.org + +## System Requirements +- Lucee 4.5+ +- ColdFusion 11+ + +## Quick Installation +Please go to our [documentation](http://logbox.ortusbooks.com) for expanded instructions. + +**CommandBox (Recommended)** + +We recommend you use [CommandBox](http://www.ortussolutions.com/products/commandbox), our CFML CLI and package manager, to install LogBox. + +**Stable Release** + +`box install logbox` + +**Bleeding Edge Release** + +`box install logbox@be` + +**Simple Install** + +Unzip the download into a folder called `logbox` in your webroot or place outside of the webroot and create a per-application mapping `/logbox` that points to it. + +**Bleeding Edge Downloads** +You can always leverage our bleeding edge artifacts server to download LogBox: http://downloads.ortussolutions.com/#/ortussolutions/logbox/ + +--- + +### THE DAILY BREAD + > "I am the way, and the truth, and the life; no one comes to the Father, but by me (JESUS)" Jn 14:1-12 diff --git a/src/cfml/system/_wirebox/system/logging/util/FileRotator.cfc b/src/cfml/system/_wirebox/system/logging/util/FileRotator.cfc new file mode 100644 index 000000000..350d307e0 --- /dev/null +++ b/src/cfml/system/_wirebox/system/logging/util/FileRotator.cfc @@ -0,0 +1,88 @@ +/** +* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp +* www.ortussolutions.com +* --- +* This utility object takes care of file log rotation +**/ +component accessors="true"{ + + /** + * Constructor + */ + function init(){ + return this; + } + + /** + * Checks the log file size. If greater than framework's settings, then zip and rotate. + * + * @appender The appender to rotate with + * @appender.docbox_generic wirebox.system.logging.AbstractAppender + */ + FileRotator function checkRotation( required appender ){ + var oAppender = arguments.appender; + var fileName = oAppender.getProperty( "fileName" ); + var logFullPath = oAppender.getLogFullPath(); + + // Verify FileSize + if ( getFileSize( logFullPath ) > ( oAppender.getProperty( "fileMaxSize" ) * 1024 ) ) { + // How Many Log Files Do we Have + var qArchivedLogs = directoryList( getDirectoryFromPath( logFullPath ), false, "query", "#filename#*.zip", "dateLastModified" ); + + lock name="#oAppender.getlockName()#" + type="exclusive" + timeout="#oAppender.getlockTimeout()#" + throwontimeout="true" + { + // Should I remove log Files + if ( qArchivedLogs.recordcount >= oAppender.getProperty( "fileMaxArchives" ) ) { + var archiveToDelete = qArchivedLogs.directory[ 1 ] & "/" & qArchivedLogs.name[ 1 ]; + // Remove the oldest one + fileDelete( archiveToDelete ); + } + + // Set the name of the archive + var zipFileName = getDirectoryFromPath( logFullPath ) & fileName & "." & dateformat( now(), "yyyymmdd" ) & "." & timeformat( now(), "HHmmss" ) & ".zip"; + + // Zip it + cfzip( + action = "zip", + file = "#zipFileName#", + overwrite = "true", + storepath = "false", + recurse = "false", + source = "#logFullPath#" + ); + } // end lock + + // Clean & reinit Log File + oAppender.removeLogFile(); + + // Reinit The log File + oAppender.initLoglocation(); + } + + return this; + } + + /** + * Get the filesize of a file. + * + * @fileName The target file + * @sizeFormat Available formats: [bytes][kbytes][mbytes][gbytes] + */ + numeric function getFileSize( required fileName, sizeFormat="bytes" ){ + // Get size in bytes + var size = getFileInfo( arguments.fileName ).size; + + if ( arguments.sizeFormat eq "bytes" ) + return size; + if ( arguments.sizeFormat eq "kbytes" ) + return ( size / 1024 ); + if ( arguments.sizeFormat eq "mbytes" ) + return ( size / 1048576 ); + if ( arguments.sizeFormat eq "gbytes" ) + return ( size / 1073741824 ); + } + +} \ No newline at end of file diff --git a/src/cfml/system/wirebox/box.json b/src/cfml/system/wirebox/box.json new file mode 100644 index 000000000..5f453b27b --- /dev/null +++ b/src/cfml/system/wirebox/box.json @@ -0,0 +1,34 @@ +{ + "name":"WireBox DI/AOP", + "version":"5.6.2+1021", + "location":"https://downloads.ortussolutions.com/ortussolutions/wirebox/5.6.2/wirebox-5.6.2.zip", + "author":"Ortus Solutions ", + "slug":"wirebox", + "type":"di", + "packageDirectory":"wirebox", + "keywords":"di,aop,dependeny injection,ioc", + "homepage":"https://www.coldbox.org", + "documentation":"https://wirebox.ortusbooks.com", + "repository":{ + "type":"git", + "url":"https://github.com/coldbox/coldbox-platform" + }, + "bugs":"https://ortussolutions.atlassian.net/browse/WIREBOX", + "shortDescription":"AOP and Dependency Injection Framework", + "license":[ + { + "type":"Apache2", + "url":"http://www.apache.org/licenses/LICENSE-2.0.html" + } + ], + "contributors":[ + "Brad Wood ", + "Curt Gratz " + ], + "ignore":[ + "**/.*", + "tests", + "apidocs", + "*/.md" + ] +} diff --git a/src/cfml/system/wirebox/system/aop/Mixer.cfc b/src/cfml/system/wirebox/system/aop/Mixer.cfc index f9b7b6ef9..437c710c2 100644 --- a/src/cfml/system/wirebox/system/aop/Mixer.cfc +++ b/src/cfml/system/wirebox/system/aop/Mixer.cfc @@ -105,17 +105,17 @@ component accessors="true"{ var idCode = variables.system.identityHashCode( target ); // Check if incoming mapping name is already class matched? - if( NOT structKeyExists( variables.classMatchDictionary, mappingName ) ){ + if( NOT variables.classMatchDictionary.containsKey( mappingName ) ){ // Register this incoming mapping for class aspect matching buildClassMatchDictionary( target, mapping, idCode ); } // Now, we check if we have any aspects to apply to this class according to class matchers - if( arrayLen( variables.classMatchDictionary[ mappingName ] ) ){ + if( arrayLen( variables.classMatchDictionary.get(mappingName) ) ){ AOPBuilder( target = target, mapping = mapping, - dictionary = variables.classMatchDictionary[ mappingName ], + dictionary = variables.classMatchDictionary.get(mappingName), idCode = idCode ); } @@ -136,7 +136,7 @@ component accessors="true"{ lock name="aop.#variables.classID#.cmd.for.#arguments.idCode#" type="exclusive" timeout="30" throwontimeout="true"{ // check again, double lock - if( NOT structKeyExists( variables.classMatchDictionary, mappingName ) ){ + if( NOT variables.classMatchDictionary.containsKey(mappingName) ){ // Discover matching for the class via all aspect bindings for( var x=1; x LTE bindingsLen; x++ ){ @@ -149,12 +149,19 @@ component accessors="true"{ }// end for discovery // Log - if( variables.log.canDebug() ){ - variables.log.debug( "Aspect class matching dictionary built for mapping: #mappingName#, aspects: #matchedAspects.toString()#" ); + if( matchedAspects.len() && variables.log.canDebug() ){ + var matchingAspects = matchedAspects.reduce( function( aggregator, thisAspect ) { + var aspectList = thisAspect.aspects; + if( isArray( aspectList ) ) { + aspectList = aspectList.toList(); + } + return aggregator.listAppend( aspectList ); + }, '' ); + variables.log.debug( "Aspect class matching dictionary built for mapping: [#mappingName#], aspects: [#matchingAspects#]" ); } // Store matched dictionary - variables.classMatchDictionary[ mappingName ] = matchedAspects; + variables.classMatchDictionary.put(mappingName, matchedAspects); } // end if in dictionary } // end lock @@ -280,9 +287,9 @@ component accessors="true"{ var udfOut = createObject( "java","java.lang.StringBuilder" ).init( '' ); var lb = "#chr( 13 )##chr( 10 )#"; var fncMD = { - name = "", - access = "public", - output ="false", + name = "", + access = "public", + output ="false", returnType = "any" }; var mappingName = arguments.mapping.getName(); @@ -290,14 +297,14 @@ component accessors="true"{ // MD proxy Defaults fncMD.name = arguments.jointPointMD.name; - if( structKeyExists( arguments.jointPointMD, "access" ) ){ - fncMD.access = arguments.jointPointMD.access; + if( structKeyExists( arguments.jointPointMD, "access" ) ){ + fncMD.access = arguments.jointPointMD.access; } - if( structKeyExists( arguments.jointPointMD, "output" ) ){ - fncMD.output = arguments.jointPointMD.output; + if( structKeyExists( arguments.jointPointMD, "output" ) ){ + fncMD.output = arguments.jointPointMD.output; } - if( structKeyExists( arguments.jointPointMD, "returntype" ) ){ - fncMD.returntype = arguments.jointPointMD.returnType; + if( structKeyExists( arguments.jointPointMD, "returntype" ) ){ + fncMD.returntype = arguments.jointPointMD.returnType; } // Create Original Method Proxy Signature if( fncMD.access eq "public" ){ @@ -305,10 +312,10 @@ component accessors="true"{ } var thisFNC = ' - <:cffunction name="aop_#hash( arguments.jointpoint )#" - access="#fncMD.access#" - output="#fncMD.output#" - returntype="#fncMD.returntype#" + <:cffunction name="aop_#hash( arguments.jointpoint )#" + access="#fncMD.access#" + output="#fncMD.output#" + returntype="#fncMD.returntype#" hint="WireBox AOP just rulez!" > @@ -334,7 +341,7 @@ component accessors="true"{ // Do : replacement, due to inline compilation avoidances thisFNC = replace( thisFNC, "<:", "<", "all" ); udfOut.append( thisFNC ); - + // MD5 Content Checks var codeSignature = hash( udfOUt.toString() ); var tmpFile = variables.properties.generationPath & "/" & codeSignature & ".cfm"; @@ -352,10 +359,10 @@ component accessors="true"{ arguments.target.$wbAOPRemove( arguments.jointpoint ); // Mix In generated aspect arguments.target.$wbAOPInclude( tmpFile ); - + // Remove Temp Aspect from disk //variables.mixerUtil.removeAspect( expandedFile ); - + // debug info if( variables.log.canDebug() ){ variables.log.debug( "Target (#mappingName#) weaved with new (#arguments.jointpoint#) method and with the following aspects: #arguments.aspects.toString()#" ); diff --git a/src/cfml/system/wirebox/system/cache/AbstractCacheBoxProvider.cfc b/src/cfml/system/wirebox/system/cache/AbstractCacheBoxProvider.cfc index fb701ff17..a18a0a43d 100644 --- a/src/cfml/system/wirebox/system/cache/AbstractCacheBoxProvider.cfc +++ b/src/cfml/system/wirebox/system/cache/AbstractCacheBoxProvider.cfc @@ -1,140 +1,452 @@ - - - - - - - // setup instance - instance = { - // cache provider name - name = "", - // enabled flag - enabled = false, - // reporting flag - reportingEnabled = false, - // stats reference will go here - stats = "", - // configuration structure - configuration = {}, - // cache factory instance - cacheFactory = "", - // event manager instance - eventManager = "", - // cache internal identifier - cacheID = createObject('java','java.lang.System').identityHashCode(this) - }; - return this; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * ---- + * An abstract CacheBox Provider + * Properties + * - name : The cache name + * - enabled : Boolean flag if cache is enabled + * - reportingEnabled: Boolean falg if cache can report + * - stats : The statistics object + * - configuration : The configuration structure + * - cacheFactory : The linkage to the cachebox factory + * - eventManager : The linkage to the event manager + * - cacheID : The unique identity code of this CFC + **/ + component accessors=true serializable=false{ + + /** + * The name of this cache provider + */ + property name="name" default=""; + /** + * Is the cache enabled or not for operation + */ + property name="enabled" type="boolean" default="false"; + /** + * Can this cache do reporting + */ + property name="reportingEnabled" type="boolean" default="false"; + /** + * The stats object linkage if any + */ + property name="stats" default=""; + /** + * The cache configuration struct + */ + property name="configuration" type="struct"; + /** + * The cache factory linkage + */ + property name="cacheFactory" default=""; + /** + * The event manager linkage + */ + property name="eventManager" default=""; + /** + * The internal cache Id + */ + property name="cacheId" default=""; + /** + * ColdBox Utility object + * @doc_generic wirebox.system.core.util.Util + */ + property name="utility"; + /** + * A Java utility to generate UUIDs + */ + property name="uuidHelper"; + + // Defaults static construct: implemented by caches + variables.DEFAULTS = {}; + + /** + * Constructor + */ + function init(){ + // cache provider name + variables.name = ""; + // enabled flag + variables.enabled = false; + // reporting flag + variables.reportingEnabled = false; + // stats reference will go here + variables.stats = ""; + // configuration structure + variables.configuration = {}; + // cache factory instance + variables.cacheFactory = ""; + // event manager instance + variables.eventManager = ""; + // cache internal identifier + variables.cacheID = createObject( 'java','java.lang.System' ).identityHashCode( this ); + // ColdBox Utility + variables.utility = new wirebox.system.core.util.Util(); + // our UUID creation helper + variables.uuidHelper = createobject( "java", "java.util.UUID" ); + + return this; + } + + /** + * Get the name of this cache + */ + function getName(){ + return variables.name; + } + + /** + * Set the cache name + * + * @name The name to set + * + * @return ICacheProvider + */ + function setName( required name ){ + variables.name = arguments.name; + return this; + } + + /** + * Returns a flag indicating if the cache is ready for operation + */ + boolean function isEnabled(){ + return variables.enabled; + } + /** + * Returns a flag indicating if the cache has reporting enabled + */ + boolean function isReportingEnabled(){ + return variables.reportingEnabled; + } + + /** + * Get the cache statistics object as wirebox.system.cache.util.IStats + * + * @return wirebox.system.cache.util.IStats + */ + function getStats(){ + return variables.stats; + } + + /** + * Clear the cache statistics + * + * @return ICacheProvider + */ + function clearStatistics(){ + variables.stats.clearStatistics(); + return this; + } + + /** + * Get the structure of configuration parameters for the cache + */ + struct function getConfiguration(){ + return variables.configuration; + } + + /** + * Set the entire configuration structure for this cache + * + * @configuration The cache configuration + * + * @return ICacheProvider + */ + function setConfiguration( required struct configuration ){ + variables.configuration = arguments.configuration; + return this; + } + + /** + * Get the cache factory reference this cache provider belongs to + */ + wirebox.system.cache.CacheFactory function getCacheFactory(){ + return variables.cacheFactory; + } + + /** + * Set the cache factory reference for this cache + * + * @cacheFactory The cache factory + * @cacheFactory.doc_generic wirebox.system.cache.CacheFactory + * + * @return ICacheProvider + */ + function setCacheFactory( required cacheFactory ){ + variables.cacheFactory = arguments.cacheFactory; + return this; + } + + /** + * Get this cache managers event listener manager + */ + function getEventManager(){ + return variables.eventManager; + } + + /** + * Set the event manager for this cache + * + * @eventManager The event manager to set + * + * @return ICacheProvider + */ + function setEventManager( required eventManager ){ + variables.eventManager = arguments.eventManager; + } + + /************************************ CACHING UTILITIES ************************************/ + + /** + * Sets Multiple Ojects in the cache. Sets might be expensive. If the JVM threshold is used and it has been reached, the object won't be cached. If the pool is at maximum it will expire using its eviction policy and still cache the object. Cleanup will be done later. + * + * @mapping The structure of name value pairs to cache + * @timeout The timeout to use on the object (if any, provider specific) + * @lastAccessTimeout The idle timeout to use on the object (if any, provider specific) + * @prefix A prefix to prepend to the keys + */ + function setMulti( + required struct mapping, + timeout="", + lastAccessTimeout="", + prefix="" + ){ + arguments.mapping.each( function( key, value ){ + // Cache these puppies + set( + objectKey = prefix & arguments.key, + object = arguments.value, + timeout = timeout, + lastAccessTimeout = lastAccessTimeout + ); + } ); + } + + /** + * Clears objects from the cache by using its cache key. The returned value is a structure of name-value pairs of all the keys that where removed from the operation. + * + * @keys The comma delimited list or array of keys to retrieve from the cache + * @prefix A prefix to prepend to the keys + */ + struct function clearMulti( required keys, prefix="" ){ + if( isSimpleValue( arguments.keys ) ){ + arguments.keys = listToArray( arguments.keys ); + } + + return arguments.keys + // prefix keys + .map( function( item ){ + return prefix & item; + }) + // reduce to struct of lookups + .reduce( function( result, key ){ + result[ key ] = clear( key ); + return result; + }, {} ); + } + + /** + * The returned value is a structure of name-value pairs of all the keys that where found or not + * + * @keys The comma delimited list or an array of keys to lookup in the cache + * @prefix A prefix to prepend to the keys with, if any + * + * @struct {key:boolean} + */ + struct function lookupMulti( required keys, prefix="" ){ + if( isSimpleValue( arguments.keys ) ){ + arguments.keys = listToArray( arguments.keys ); + } + + return arguments.keys + // prefix keys + .map( function( item ){ + return prefix & item; + }) + // reduce to struct of lookups + .reduce( function( result, key ){ + result[ key ] = lookup( key ); + return result; + }, {} ); + } + + /** + * The returned value is a structure of name-value pairs of all the keys that where found. Not found values will be in the mapping as null + * + * @keys The comma delimited list or an array of keys to lookup in the cache + * @prefix A prefix to prepend to the keys with, if any + * + * @struct {key:boolean} + */ + struct function getMulti( required keys, prefix="" ){ + if( isSimpleValue( arguments.keys ) ){ + arguments.keys = listToArray( arguments.keys ); + } + + return arguments.keys + // prefix keys + .map( function( item ){ + return prefix & item; + }) + // reduce to struct of lookups + .reduce( function( result, key ){ + result[ key ] = get( key ); + return result; + }, {} ); + } + + /** + * Get the cached object's metadata structure. If the object does not exist, it returns an empty structure. + * + * @keys The comma delimited list or array of keys to retrieve from the cache + * @prefix A prefix to prepend to the keys + */ + struct function getCachedObjectMetadataMulti( required keys, prefix="" ){ + if( isSimpleValue( arguments.keys ) ){ + arguments.keys = listToArray( arguments.keys ); + } + + return arguments.keys + // prefix keys + .map( function( item ){ + return prefix & item; + }) + // reduce to struct of lookups + .reduce( function( result, key ){ + result[ key ] = getCachedObjectMetadata( key ); + return result; + }, {} ); + } + + /** + * Clear by key snippet + * + * @keySnippet The key snippet partial to clear out + * @regex Wethere to use regex matching or not, defaults to false + * @async To do this in async mode or sync mode, defaults to false + * + * @return LuceeProvider + */ + function clearByKeySnippet( required keySnippet, boolean regex=false, boolean async=false ){ + var threadName = "clearByKeySnippet_#replace( randomUUID(), "-", "", "all" )#"; + + // Async? IF so, do checks + if( arguments.async AND NOT inThread() ){ + thread name="#threadName#" keySnippet="#arguments.keySnippet#" regex="#arguments.regex#"{ + variables.elementCleaner.clearByKeySnippet( attribues.keySnippet, attribues.regex ); + } + } else{ + variables.elementCleaner.clearByKeySnippet( arguments.keySnippet, arguments.regex ); + } + + return this; + } + + /** + * Tries to get an object from the cache, if not found, it calls the 'produce' closure to produce the data and cache it + * + * @objectKey The object cache key + * @produce The producer closure/lambda + * @timeout The timeout to use on the object (if any, provider specific) + * @lastAccessTimeout The idle timeout to use on the object (if any, provider specific) + * @extra A map of name-value pairs to use as extra arguments to pass to a providers set operation + * + * @return The cached or produced data/object + */ + any function getOrSet( + required any objectKey, + required any produce, + any timeout="", + any lastAccessTimeout="", + any extra={} + ){ + + // Verify if it exists? if so, return it. + var target = get( arguments.objectKey ); + if( !isNull( local.target ) ){ + return target; + } + + // else, produce it + lock name="GetOrSet.#variables.cacheID#.#arguments.objectKey#" type="exclusive" timeout="45" throwonTimeout="true"{ + // double lock, due to race conditions + var target = get( arguments.objectKey ); + if( isNull( local.target ) ){ + // produce it + target = arguments.produce(); + // store it + set( + objectKey = arguments.objectKey, + object = target, + timeout = arguments.timeout, + lastAccessTimeout = arguments.lastAccessTimeout, + extra = arguments.extra + ); + } + } + + return target; + } + + /************************************ UTILITIES ************************************/ + + /** + * Produce a fast random UUID + */ + function randomUUID(){ + return variables.uuidHelper.randomUUID(); + } + + /** + * A quick snapshot of the state + */ + struct function getMemento(){ + return variables.filter( function( k, v ){ + return ( !isCustomFunction( v ) ); + } ); + } + + /** + * Verifies if the request is in the current thread or another spawned thread + */ + boolean function inThread(){ + return variables.utility.inThread(); + } + + /************************************ PRIVATE ************************************/ + + /** + * Check if the cache is operational, else throw exception + * + * @throws IllegalStateException + */ + private AbstractCacheBoxProvider function statusCheck(){ + if( !isEnabled ){ + throw( + message = "The cache #getName()# is not yet enabled", + detail = "The cache was being accessed without the configuration being complete", + type = "IllegalStateException" + ); + } + } + + /** + * Validate the incoming configuration and make necessary defaults + * + * @return AbstractCacheProvider + **/ + private function validateConfiguration(){ + // Add in settings not discovered + structAppend( variables.configuration, variables.DEFAULTS, false ); + // Validate configuration values, if they don't exist, then default them to DEFAULTS + for( var key in variables.DEFAULTS ){ + if( NOT len( variables.configuration[ key ] ) ){ + variables.configuration[ key ] = variables.DEFAULTS[ key ]; + } + } + + return this; + } + + } \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/CacheFactory.cfc b/src/cfml/system/wirebox/system/cache/CacheFactory.cfc index 11b005c81..b869bee1d 100644 --- a/src/cfml/system/wirebox/system/cache/CacheFactory.cfc +++ b/src/cfml/system/wirebox/system/cache/CacheFactory.cfc @@ -1,687 +1,705 @@ - - - - - - - - - - - - var defaultConfigPath = "wirebox.system.cache.config.DefaultConfiguration"; - - // Prepare factory instance - instance = { - // CacheBox Factory UniqueID - factoryID = createObject('java','java.lang.System').identityHashCode(this), - // Version - version = "5.1.4+741", - // Configuration object - config = "", - // ColdBox Application Link - coldbox = "", - // Event Manager Link - eventManager = "", - // Configured Event States - eventStates = [ - "afterCacheElementInsert", - "afterCacheElementRemoved", - "afterCacheElementExpired", - "afterCacheElementUpdated", - "afterCacheClearAll", - "afterCacheRegistration", - "afterCacheRemoval", - "beforeCacheRemoval", - "beforeCacheReplacement", - "afterCacheFactoryConfiguration", - "beforeCacheFactoryShutdown", - "afterCacheFactoryShutdown", - "beforeCacheShutdown", - "afterCacheShutdown" - ], - // LogBox Links - logBox = "", - log = "", - // Caches - caches = {} - }; - - // Did we send a factoryID in? - if( len(arguments.factoryID) ){ - instance.factoryID = arguments.factoryID; - } - - // Prepare Lock Info - instance.lockName = "CacheFactory.#instance.factoryID#"; - - // Passed in configuration? - if( NOT structKeyExists( arguments, "config" ) ){ - // Create default configuration - arguments.config = createObject( "component", "wirebox.system.cache.config.CacheBoxConfig" ).init( CFCConfigPath=defaultConfigPath ); - } - else if( isSimpleValue( arguments.config ) ){ - arguments.config = createObject( "component", "wirebox.system.cache.config.CacheBoxConfig" ).init( CFCConfigPath=arguments.config ); - } - - // Check if linking ColdBox - if( structKeyExists(arguments, "coldbox") ){ - // Link ColdBox - instance.coldbox = arguments.coldbox; - // link LogBox - instance.logBox = instance.coldbox.getLogBox(); - // Link Event Manager - instance.eventManager = instance.coldbox.getInterceptorService(); - // Link Interception States - instance.coldbox.getInterceptorService().appendInterceptionPoints( arrayToList(instance.eventStates) ); - } - else{ - // Running standalone, so create our own logging first - configureLogBox( arguments.config.getLogBoxConfig() ); - // Running standalone, so create our own event manager - configureEventManager(); - } - - // Configure Logging for the Cache Factory - instance.log = instance.logBox.getLogger( this ); - - // Configure the Cache Factory - configure( arguments.config ); - - return this; - - - - - - - - var listeners = instance.config.getListeners(); - var regLen = arrayLen(listeners); - var x = 1; - var thisListener = ""; - - // iterate and register listeners - for(x=1; x lte regLen; x++){ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * ---- + * @author Luis Majano + * + * The main CacheBox factory and configuration of caches. From this factory + * is where you will get all the caches you need to work with or register more caches. + **/ + component accessors=true serializable=false{ + + /** + * The unique factory id + */ + property name="factoryId"; + /** + * The factory version + */ + property name="version"; + /** + * The CacheBox Configuration object linkage + */ + property name="config"; + /** + * The ColdBox object linkage + */ + property name="coldbox"; + /** + * The LogBox object linkage + */ + property name="logbox"; + /** + * A configured log class + */ + property name="log"; + /** + * The Event Manager object linkage + */ + property name="eventManager"; + /** + * The array of events this factory registers + */ + property name="eventStates" type="array"; + /** + * The registered caches this factory keeps track of + */ + property name="caches" type="struct"; + + /** + * Constructor + * + * @config The CacheBoxConfig object or path to use to configure this instance of CacheBox. If not passed then CacheBox will instantiate the default configuration. + * @config.doc_generic wirebox.system.cache.config.CacheBoxConfig + * @coldbox A coldbox application that this instance of CacheBox can be linked to, if not using it, just ignore it. + * @coldbox.doc_generic wirebox.system.web.Controller + * @factoryID A unique ID or name for this factory. If not passed I will make one up for you. + */ + function init( + config, + coldbox, + factoryId="" + ){ + var defaultConfigPath = "wirebox.system.cache.config.DefaultConfiguration"; + + // CacheBox Factory UniqueID + variables.factoryId = createObject( "java", "java.lang.System" ).identityHashCode( this ); + // Version + variables.version = "5.6.2+1021"; + // Configuration object + variables.config = ""; + // ColdBox Application Link + variables.coldbox = ""; + // Event Manager Link + variables.eventManager = ""; + // Configured Event States + variables.eventStates = [ + "afterCacheElementInsert", + "afterCacheElementRemoved", + "afterCacheElementExpired", + "afterCacheElementUpdated", + "afterCacheClearAll", + "afterCacheRegistration", + "afterCacheRemoval", + "beforeCacheRemoval", + "beforeCacheReplacement", + "afterCacheFactoryConfiguration", + "beforeCacheFactoryShutdown", + "afterCacheFactoryShutdown", + "beforeCacheShutdown", + "afterCacheShutdown" + ]; + // LogBox Links + variables.logBox = ""; + variables.log = ""; + // Caches + variables.caches = {}; + + // Did we send a factoryID in? + if( len( arguments.factoryID ) ){ + variables.factoryID = arguments.factoryID; + } + + // Prepare Lock Info + variables.lockName = "CacheFactory.#variables.factoryID#"; + + // Passed in configuration? + if( isNull( arguments.config ) ){ + // Create default configuration + arguments.config = new wirebox.system.cache.config.CacheBoxConfig( CFCConfigPath=defaultConfigPath ); + } else if( isSimpleValue( arguments.config ) ){ + arguments.config = new wirebox.system.cache.config.CacheBoxConfig( CFCConfigPath=arguments.config ); + } + + // Check if linking ColdBox + if( !isNull( arguments.coldbox ) ){ + // Link ColdBox + variables.coldbox = arguments.coldbox; + // link LogBox + variables.logBox = variables.coldbox.getLogBox(); + // Link Event Manager + variables.eventManager = variables.coldbox.getInterceptorService(); + // Link Interception States + variables.coldbox.getInterceptorService().appendInterceptionPoints( variables.eventStates ); + } else { + // Running standalone, so create our own logging first + configureLogBox( arguments.config.getLogBoxConfig() ); + // Running standalone, so create our own event manager + configureEventManager(); + } + + // Configure Logging for the Cache Factory + variables.log = variables.logBox.getLogger( this ); + + // Configure the Cache Factory + configure( arguments.config ); + + return this; + } + + /** + * Register all the configured listeners in the configuration file + * + * @throws CacheBox.ListenerCreationException + */ + CacheFactory function registerListeners(){ + variables.config.getListeners() + .each( function( item ){ // try to create it try{ // create it - thisListener = createObject("component", listeners[x].class); + var thisListener = createObject( "component", item.class ); // configure it - thisListener.configure( this, listeners[x].properties); - } - catch(Any e){ - throw(message="Error creating listener: #listeners[x].toString()#", - detail="#e.message# #e.detail# #e.stackTrace#", - type="CacheBox.ListenerCreationException"); + thisListener.configure( this, item.properties ); + } catch( Any e ){ + throw( + message = "Error creating listener: #item.toString()#", + detail = "#e.message# #e.detail# #e.stackTrace#", + type = "CacheBox.ListenerCreationException" + ); } - - // Now register listener - instance.eventManager.register(thisListener,listeners[x].name); - - } - - - - - - - - var defaultCacheConfig = ""; - var caches = ""; - var key = ""; - var iData = {}; - - - - + // Now register listener with the event manager + variables.eventManager.register( thisListener, item.name ); + } ); + + return this; + } + + /** + * Configure the cache factory for operation, called by the init(). You can also re-configure CacheBox programmatically. + * + * @config The CacheBox config object + * @config.doc_generic wirebox.system.cache.config.CacheBoxConfig + */ + function configure( required config ){ + lock + name="#variables.lockName#" + type="exclusive" + timeout="30" + throwontimeout="true" + { // Store config object - instance.config = arguments.config; + variables.config = arguments.config; // Validate configuration - instance.config.validate(); + variables.config.validate(); // Reset Registries - instance.caches = {}; + variables.caches = {}; // Register Listeners if not using ColdBox - if( not isObject(instance.coldbox) ){ + if( not isObject( variables.coldbox ) ){ registerListeners(); } // Register default cache first - defaultCacheConfig = instance.config.getDefaultCache(); - createCache(name="default",provider=defaultCacheConfig.provider,properties=defaultCacheConfig); + var defaultCacheConfig = variables.config.getDefaultCache(); + createCache( + name = "default", + provider = defaultCacheConfig.provider, + properties = defaultCacheConfig + ); // Register named caches - caches = instance.config.getCaches(); - for(key in caches){ - createCache(name=key,provider=caches[key].provider,properties=caches[key].properties); - } + variables.config.getCaches() + .each( function( key, def ){ + createCache( + name = key, + provider = def.provider, + properties = def.properties + ); + }); // Scope registrations - if( instance.config.getScopeRegistration().enabled ){ + if( variables.config.getScopeRegistration().enabled ){ doScopeRegistration(); } // Announce To Listeners - iData.cacheFactory = this; - instance.eventManager.processState("afterCacheFactoryConfiguration",iData); - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - var defaultCacheConfig = instance.config.getDefaultCache(); - - // Check length - if( len(arguments.name) eq 0 ){ - throw(message="Invalid Cache Name", - detail="The name you sent in is invalid as it was blank, please send in a name", - type="CacheFactory.InvalidNameException"); - } - - // Check it does not exist already - if( cacheExists( arguments.name ) ){ - throw(message="Cache #arguments.name# already exists", - detail="Cannot register named cache as it already exists in the registry", - type="CacheFactory.CacheExistsException"); - } - - // Create default cache instance - cache = createCache(name=arguments.name,provider=defaultCacheConfig.provider,properties=defaultCacheConfig); - - // Return it - return cache; - - - - - - - var iData = {}; - var cacheNames = getCacheNames(); - var cacheLen = arraylen(cacheNames); - var cache = ""; - var i = 1; - - // Log startup - if( instance.log.canDebug() ){ - instance.log.debug("Shutdown of cache factory: #getFactoryID()# requested and started."); - } - - // Notify Listeners - iData = {cacheFactory=this}; - instance.eventManager.processState("beforeCacheFactoryShutdown",iData); - - // safely iterate and shutdown caches - for( i=1; i lte cacheLen; i++){ - - // Get cache to shutdown - cache = getCache( cacheNames[i] ); - - // Log it - if( instance.log.canDebug() ){ - instance.log.debug("Shutting down cache: #cacheNames[i]# on factoryID: #getFactoryID()#."); - } - - //process listners - iData = {cache=cache}; - instance.eventManager.processState("beforeCacheShutdown",iData); - - //Shutdown each cache - cache.shutdown(); - - //process listeners - instance.eventManager.processState("afterCacheShutdown",iData); - - // log - if( instance.log.canDebug() ){ - instance.log.debug("Cache: #cacheNames[i]# was shut down on factoryID: #getFactoryID()#."); - } - } - - // Remove all caches - removeAll(); - - // remove scope registration - removeFromScope(); - - // Notify Listeners - iData = {cacheFactory=this}; - instance.eventManager.processState("afterCacheFactoryShutdown",iData); - - // Log shutdown complete - if( instance.log.canDebug() ){ - instance.log.debug("Shutdown of cache factory: #getFactoryID()# completed."); - } - - - - - - - - var iData = {}; - var cache = ""; - var i = 1; - - // Check if cache exists, else exit out - if( NOT cacheExists(arguments.name) ){ - if( instance.log.canWarn() ){ - instance.log.warn("Trying to shutdown #arguments.name#, but that cache does not exist, skipping."); - } - return; + variables.eventManager.processState( "afterCacheFactoryConfiguration", { cacheFactory = this } ); + } + } + + /********************************* PUBLIC CACHE FACTORY OPERATIONS *********************************/ + + /** + * Get a reference to a registered cache in this factory. If the cache does not exist it will return an exception. Type: wirebox.system.cache.providers.ICacheProvider + * + * @name The cache name to get + * + * @throws CacheFactory.CacheNotFoundException + * @return wirebox.system.cache.providers.ICacheProvider + */ + function getCache( required name ){ + lock name="#variables.lockName#" type="readonly" timeout="20" throwontimeout="true"{ + if( variables.caches.keyExists( arguments.name ) ){ + return variables.caches[ arguments.name ]; } + throw( + message = "Cache #arguments.name# is not registered.", + detail = "Valid cache names are #structKeyList( variables.caches )#", + type = "CacheFactory.CacheNotFoundException" + ); + } + } + + /** + * Register a new instantiated cache with this cache factory + * + * @cache The cache to register + * @cache.doc_generic wirebox.system.cache.providers.ICacheProvider + */ + CacheFactory function addCache( required cache ){ + registerCache( arguments.cache ); + return this; + } + + /** + * Add a default named cache to our registry, create it, config it, register it and return it of type: wirebox.system.cache.providers.ICacheProvider + * + * @name The name of the default cache to create + * + * @throw CacheFactory.InvalidNameException,CacheFactory.CacheExistsException + * @return wirebox.system.cache.providers.ICacheProvider + */ + function addDefaultCache( required name ){ + var defaultCacheConfig = variables.config.getDefaultCache(); + + // Check length + if( len( arguments.name ) eq 0 ){ + throw( + message = "Invalid Cache Name", + detail = "The name you sent in is invalid as it was blank, please send in a name", + type = "CacheFactory.InvalidNameException" + ); + } + + // Check it does not exist already + if( cacheExists( arguments.name ) ){ + throw( + message = "Cache #arguments.name# already exists", + detail = "Cannot register named cache as it already exists in the registry", + type = "CacheFactory.CacheExistsException" + ); + } + + // Create default cache instance + return createCache( + name = arguments.name, + provider = defaultCacheConfig.provider, + properties = defaultCacheConfig + ); + } + + /** + * Recursively sends shutdown commands to al registered caches and cleans up in preparation for shutdown + */ + CacheFactory function shutdown(){ + // Log startup + if( variables.log.canDebug() ){ + variables.log.debug( "Shutdown of cache factory: #getFactoryID()# requested and started." ); + } + + // Notify Listeners + variables.eventManager.processState( "beforeCacheFactoryShutdown", { cacheFactory = this } ); + + // safely iterate and shutdown caches + getCacheNames().each( function( item ){ + // Get cache to shutdown + var cache = getCache( item ); - //get Cache - cache = getCache(arguments.name); - - // log it - if( instance.log.canDebug() ){ - instance.log.debug("Shutdown of cache: #arguments.name# requested and started on factoryID: #getFactoryID()#"); + // Log it + if( variables.log.canDebug() ){ + variables.log.debug( "Shutting down cache: #item# on factoryID: #getFactoryID()#." ); } - // Notify Listeners - iData = {cache=cache}; - instance.eventManager.processState("beforeCacheShutdown",iData); + //process listners + variables.eventManager.processState( "beforeCacheShutdown", { cache = cache } ); - //Shutdown the cache + //Shutdown each cache cache.shutdown(); //process listeners - instance.eventManager.processState("afterCacheShutdown",iData); + variables.eventManager.processState( "afterCacheShutdown", { cache = cache } ); - // remove cache - removeCache(arguments.name); - - // Log it - if( instance.log.canDebug() ){ - instance.log.debug("Cache: #arguments.name# was shut down and removed on factoryID: #getFactoryID()#."); + // log + if( variables.log.canDebug() ){ + variables.log.debug( "Cache: #item# was shut down on factoryID: #getFactoryID()#." ); } - - - - - - - var scopeInfo = instance.config.getScopeRegistration(); - if( scopeInfo.enabled ){ - createObject("component","wirebox.system.core.collections.ScopeStorage").init().delete(scopeInfo.key, scopeInfo.scope); + } ); + + // Remove all caches + removeAll(); + + // remove scope registration + removeFromScope(); + + // Notify Listeners + variables.eventManager.processState( "afterCacheFactoryShutdown", { cacheFactory = this } ); + + // Log shutdown complete + if( variables.log.canDebug() ){ + variables.log.debug( "Shutdown of cache factory: #getFactoryID()# completed." ); + } + + return this; + } + + /** + * Send a shutdown command to a specific cache provider to bring down gracefully. It also removes it from the cache factory + * + * @name The name of the cache to shutdown + */ + CacheFactory function shutdownCache( required name ){ + var iData = {}; + var cache = ""; + var i = 1; + + // Check if cache exists, else exit out + if( NOT cacheExists( arguments.name ) ){ + if( variables.log.canWarn() ){ + variables.log.warn( "Trying to shutdown #arguments.name#, but that cache does not exist, skipping." ); } - - - - - - - - - - - - - // double check - if( structKeyExists( instance.caches, arguments.name ) ){ - - //Log - if( instance.log.canDebug() ){ - instance.log.debug("Cache: #arguments.name# asked to be removed from factory: #getFactoryID()#"); - } - - // Retrieve it - cache = instance.caches[ arguments.name ]; + return this; + } + + // get Cache + var cache = getCache( arguments.name ); + + // log it + if( variables.log.canDebug() ){ + variables.log.debug( "Shutdown of cache: #arguments.name# requested and started on factoryID: #getFactoryID()#" ); + } + + // Notify Listeners + variables.eventManager.processState( "beforeCacheShutdown", { cache = cache } ); + + //Shutdown the cache + cache.shutdown(); + + //process listeners + variables.eventManager.processState( "afterCacheShutdown", { cache = cache } ); + + // remove cache + removeCache( arguments.name ); + + // Log it + if( variables.log.canDebug() ){ + variables.log.debug( "Cache: #arguments.name# was shut down and removed on factoryID: #getFactoryID()#." ); + } + + return this; + } + + /** + * Remove the cache factory from scope registration if enabled, else does nothing + */ + CacheFactory function removeFromScope(){ + var scopeInfo = variables.config.getScopeRegistration(); + if( scopeInfo.enabled ){ + new wirebox.system.core.collections.ScopeStorage() + .delete( scopeInfo.key, scopeInfo.scope ); + } + return this; + } + + /** + * Try to remove a named cache from this factory, returns Boolean if successfull or not + * + * @name The name of the cache to remove + */ + boolean function removeCache( required name ){ + if( cacheExists( arguments.name ) ){ + lock name="#variables.lockName#" type="exclusive" timeout="20" throwontimeout="true"{ + if( cacheExists( arguments.name ) ){ + //Log + if( variables.log.canDebug() ){ + variables.log.debug( "Cache: #arguments.name# asked to be removed from factory: #getFactoryID()#" ); + } - // Notify listeners here - iData.cache = cache; - instance.eventManager.processState("beforeCacheRemoval",iData); + // Retrieve it + var cache = variables.caches[ arguments.name ]; - // process shutdown - cache.shutdown(); + // Notify listeners here + variables.eventManager.processState( "beforeCacheRemoval", { cache = cache } ); - // Remove it - structDelete( instance.caches, arguments.name ); + // process shutdown + cache.shutdown(); - // Announce it - iData.cache = arguments.name; - instance.eventManager.processState("afterCacheRemoval",iData); + // Remove it + structDelete( variables.caches, arguments.name ); - // Log it - if( instance.log.canDebug() ){ - instance.log.debug("Cache: #arguments.name# removed from factory: #getFactoryID()#"); - } + // Announce it + variables.eventManager.processState( "afterCacheRemoval", { cache = arguments.name } ); - return true; + // Log it + if( variables.log.canDebug() ){ + variables.log.debug( "Cache: #arguments.name# removed from factory: #getFactoryID()#" ); } - - - - - - - - - - - - - - - var cacheNames = getCacheNames(); - var cacheLen = arraylen(cacheNames); - var i = 1; - - if( instance.log.canDebug() ){ - instance.log.debug("Removal of all caches requested on factoryID: #getFactoryID()#"); - } - - for( i=1; i lte cacheLen; i++){ - removeCache( cacheNames[i] ); - } - - if( instance.log.canDebug() ){ - instance.log.debug("All caches removed."); - } - - - - - - - var cacheNames = getCacheNames(); - var cacheLen = arraylen(cacheNames); - var i = 1; - var cache = ""; - - if( instance.log.canDebug() ){ - instance.log.debug("Executing reap on factoryID: #getFactoryID()#"); - } - for( i=1; i lte cacheLen; i++){ - cache = getCache( cacheNames[i] ); - cache.reap(); + return true; + } } - - - - - - - - - - + } + + if( variables.log.canDebug() ){ + variables.log.debug( "Cache: #arguments.name# not removed because it does not exist in registered caches: #arrayToList( getCacheNames() )#. FactoryID: #getFactoryID()#" ); + } + + return false; + } + + /** + * Remove all the registered caches in this factory, this triggers individual cache shutdowns + */ + CacheFactory function removeAll(){ + if( variables.log.canDebug() ){ + variables.log.debug( "Removal of all caches requested on factoryID: #getFactoryID()#" ); + } + + getCacheNames().each( function( item ){ + removeCache( item ); + } ); + + + if( variables.log.canDebug() ){ + variables.log.debug( "All caches removed." ); + } + + return this; + } + + /** + * A nice way to call reap on all registered caches + */ + CacheFactory function reapAll(){ + if( variables.log.canDebug() ){ + variables.log.debug( "Executing reap on factoryID: #getFactoryID()#" ); + } + + getCacheNames().each( function( item ){ + getCache( item ).reap(); + } ); + + return this; + } + + /** + * Check if the passed in named cache is already registered in this factory or not + */ + boolean function cacheExists( required name ){ + lock name="#variables.lockName#" type="readonly" timeout="20" throwontimeout="true"{ + return structKeyExists( variables.caches, arguments.name ); + } + } + + /** + * Replace a registered named cache with a new decorated cache of the same name. + * + * @cache The name of the cache to replace or the actual instance of the cache to replace + * @cache.doc_generic wirebox.system.cache.providers.ICacheProvider or string + * @decoratedCache The decorated cache manager instance to replace with of type wirebox.system.cache.providers.ICacheProvider + * @decoratedCache.doc_generic wirebox.system.cache.providers.ICacheProvider + */ + CacheFactory function replaceCache( required cache, required decoratedCache ){ + // determine cache name + if( isObject( arguments.cache ) ){ + var name = arguments.cache.getName(); + } else { + var name = arguments.cache; + } + + lock name="#variables.lockName#" type="exclusive" timeout="20" throwontimeout="true"{ + // Announce to listeners + var iData = { + oldCache = variables.caches[ name ], + newCache = arguments.decoratedCache + }; - + variables.eventManager.processState( "beforeCacheReplacement", iData ); - - - - + // remove old Cache + structDelete( variables.caches, name ); - - var name = ""; - var iData = {}; + // Replace it + variables.caches[ name ] = arguments.decoratedCache; - // determine cache name - if( isObject(arguments.cache) ){ - name = arguments.cache.getName(); + // debugging + if( variables.log.canDebug() ){ + variables.log.debug( "Cache #name# replaced with decorated cache: #getMetadata( arguments.decoratedCache ).name# on factoryID: #getFactoryID()#" ); } - else{ - name = arguments.cache; - } - - - - - // Announce to listeners - iData.oldCache = instance.caches[name]; - iData.newCache = arguments.decoratedCache; - instance.eventManager.processState("beforeCacheReplacement",iData); - - // remove old Cache - structDelete( instance.caches, name); - // Replace it - instance.caches[ name ] = arguments.decoratedCache; - - // debugging - if( instance.log.canDebug() ){ - instance.log.debug("Cache #name# replaced with decorated cache: #getMetadata(arguments.decoratedCache).name# on factoryID: #getFactoryID()#"); + } + + return this; + } + + /** + * Clears all the elements in all the registered caches without de-registrations + */ + CacheFactory function clearAll(){ + if( variables.log.canDebug() ){ + variables.log.debug( "Clearing all registered caches of their content on factoryID: #getFactoryID()#" ); + } + + getCacheNames().each( function( item ){ + getCache( item ).clearAll(); + } ); + + return this; + } + + /** + * Expires all the elements in all the registered caches without de-registrations + */ + CacheFactory function expireAll(){ + if( variables.log.canDebug() ){ + variables.log.debug( "Expiring all registered caches of their content on factoryID: #getFactoryID()#" ); + } + + getCacheNames().each( function( item ){ + getCache( item ).expireAll(); + } ); + + return this; + } + + /** + * Get the array of caches registered with this factory + */ + array function getCacheNames(){ + lock name="#variables.lockName#" type="readonly" timeout="20" throwontimeout="true"{ + return structKeyArray( variables.caches ); + } + } + + /** + * Checks if Coldbox application controller is linked + */ + boolean function isColdBoxLinked(){ + return isObject( variables.coldbox ); + } + + /** + * Get the default cache provider of type wirebox.system.cache.providers.ICacheProvider" + * + * @return wirebox.system.cache.providers.ICacheProvider + */ + function getDefaultCache(){ + return getCache( "default" ); + } + + /** + * Get the scope registration information + */ + struct function getScopeRegistration(){ + return variables.config.getScopeRegistration(); + } + + /** + * Create a new cache according the the arguments, register it and return it of type: wirebox.system.cache.providers.ICacheProvider + * + * @name The name of the cache to create + * @provider The provider class path of the cache to add + * @properties The configuration properties of the cache + * + * @return wirebox.system.cache.providers.ICacheProvider + */ + function createCache( required name, required provider, struct properties={} ){ + // Create Cache + var oCache = createObject( "component", arguments.provider ).init(); + // Register Name + oCache.setName( arguments.name ); + // Link Properties + oCache.setConfiguration( arguments.properties ); + // Register Cache + registerCache( oCache ); + + return oCache; + } + + /*************************** PRIVATE METHODS ******************************/ + + /** + * Register this cachefactory on a user specified scope + */ + private CacheFactory function doScopeRegistration(){ + var scopeInfo = variables.config.getScopeRegistration(); + new wirebox.system.core.collections.ScopeStorage() + .put( scopeInfo.key, this, scopeInfo.scope ); + return this; + } + + /** + * Register a new cache on this cache factory + * + * @cache The cache instance to register with this factory of type: wirebox.system.cache.providers.ICacheProvider + * @cache.doc_generic wirebox.system.cache.providers.ICacheProvider + * + * @throws CacheFactory.CacheExistsException + */ + private CacheFactory function registerCache( required cache ){ + var name = arguments.cache.getName(); + var oCache = arguments.cache; + + if( variables.caches.keyExists( name ) ){ + throw( + message = "Cache #name# already exists!", + type = "CacheFactory.CacheExistsException" + ); + } + + // Verify Registration + if( !variables.caches.keyExists( name ) ){ + lock name="#variables.lockName#" type="exclusive" timeout="20" throwontimeout="true"{ + if( !variables.caches.keyExists( name ) ){ + // Link to this CacheFactory + oCache.setCacheFactory( this ); + // Link ColdBox if using it + if( isObject( variables.coldbox ) AND structKeyExists( oCache, "setColdBox" ) ){ + oCache.setColdBox( variables.coldbox ); + } + // Link Event Manager + oCache.setEventManager( variables.eventManager ); + // Call Configure it to start the cache up + oCache.configure(); + // Store it + variables.caches[ name ] = oCache; + // Announce new cache registration now + variables.eventManager.processState( "afterCacheRegistration", { cache = oCache } ); } - - - - - - - - - var cacheNames = getCacheNames(); - var cacheLen = arraylen(cacheNames); - var i = 1; - var cache = ""; - - if( instance.log.canDebug() ){ - instance.log.debug("Clearing all registered caches of their content on factoryID: #getFactoryID()#"); - } - - for( i=1; i lte cacheLen; i++){ - cache = getCache( cacheNames[i] ); - cache.clearAll(); } - - - - - - - var cacheNames = getCacheNames(); - var cacheLen = arraylen(cacheNames); - var i = 1; - var cache = ""; - - if( instance.log.canDebug() ){ - instance.log.debug("Expiring all registered caches of their content on factoryID: #getFactoryID()#"); - } - - for( i=1; i lte cacheLen; i++){ - cache = getCache( cacheNames[i] ); - cache.expireAll(); - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Create Cache - var oCache = createObject("component",arguments.provider).init(); - // Register Name - oCache.setName( arguments.name ); - // Link Properties - oCache.setConfiguration( arguments.properties ); - // Register Cache - registerCache( oCache ); - - return oCache; - - - - - - - - - var scopeInfo = instance.config.getScopeRegistration(); - var scopeStorage = createObject("component","wirebox.system.core.collections.ScopeStorage").init(); - // register factory with scope - scopeStorage.put(scopeInfo.key, this, scopeInfo.scope); - - - - - - - - - - - - - - - - - - - - if( NOT structKeyExists(instance.caches, name) ){ - // Link to this CacheFactory - oCache.setCacheFactory( this ); - // Link ColdBox if using it - if( isObject(instance.coldbox) AND structKeyExists(oCache,"setColdBox")){ - oCache.setColdBox( instance.coldbox ); - } - // Link Event Manager - oCache.setEventManager( instance.eventManager ); - // Call Configure it to start the cache up - oCache.configure(); - // Store it - instance.caches[ name ] = oCache; - - // Announce new cache registration now - iData.cache = oCache; - instance.eventManager.processState("afterCacheRegistration",iData); - } - - - - - - - - - - // Create config object - var oConfig = new wirebox.system.logging.config.LogBoxConfig( CFCConfigPath = arguments.configPath ); - // Create LogBox standalone and store it - instance.logBox = new wirebox.system.logging.LogBox( oConfig ); - - return this; - - - - - - - // create event manager - instance.eventManager = createObject("component","wirebox.system.core.events.EventPoolManager").init( instance.eventStates ); - // register the points to listen to - instance.eventManager.appendInterceptionPoints( arrayToList(instance.eventStates) ); - - - - - - - - - \ No newline at end of file + } + + return this; + } + + /** + * Configure a standalone version of logBox for logging + * + * @configPath The LogBox configuration CFC path + */ + private CacheFactory function configureLogBox( required configPath ){ + // Create config object + var oConfig = new wirebox.system.logging.config.LogBoxConfig( CFCConfigPath = arguments.configPath ); + // Create LogBox standalone and store it + variables.logBox = new wirebox.system.logging.LogBox( oConfig ); + return this; + } + + /** + * Configure a standalone version of a ColdBox Event Manager + */ + private CacheFactory function configureEventManager(){ + // create event manager + variables.eventManager = new wirebox.system.core.events.EventPoolManager( variables.eventStates ); + // register the points to listen to + variables.eventManager.appendInterceptionPoints( variables.eventStates ); + return this; + } + + /** + * Get a new utility object + */ + function getUtil(){ + return new wirebox.system.core.util.Util(); + } + + } \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/ICacheProvider.cfc b/src/cfml/system/wirebox/system/cache/ICacheProvider.cfc index e17308ed0..e08acda6a 100644 --- a/src/cfml/system/wirebox/system/cache/ICacheProvider.cfc +++ b/src/cfml/system/wirebox/system/cache/ICacheProvider.cfc @@ -1,4 +1,4 @@ - - + - + @@ -27,7 +27,7 @@ Description : - + @@ -35,11 +35,11 @@ Description : - + - + @@ -52,7 +52,7 @@ Description : - + @@ -61,7 +61,7 @@ Description : - + @@ -70,59 +70,59 @@ Description : - + - + - + - + - + - + - + - + - - + + - + - - + + - + @@ -132,7 +132,7 @@ Description : - + @@ -142,7 +142,7 @@ Description : - + @@ -159,16 +159,16 @@ Description : - + - + - + diff --git a/src/cfml/system/wirebox/system/cache/IColdboxApplicationCache.cfc b/src/cfml/system/wirebox/system/cache/IColdboxApplicationCache.cfc index aa6314ced..ded4a6fcd 100644 --- a/src/cfml/system/wirebox/system/cache/IColdboxApplicationCache.cfc +++ b/src/cfml/system/wirebox/system/cache/IColdboxApplicationCache.cfc @@ -1,4 +1,4 @@ - - + @@ -36,24 +36,24 @@ Description : - + - + - + - + @@ -63,5 +63,5 @@ Description : - + \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/config/CacheBoxConfig.cfc b/src/cfml/system/wirebox/system/cache/config/CacheBoxConfig.cfc index 4b95406be..48a184773 100644 --- a/src/cfml/system/wirebox/system/cache/config/CacheBoxConfig.cfc +++ b/src/cfml/system/wirebox/system/cache/config/CacheBoxConfig.cfc @@ -1,286 +1,324 @@ - - - - - // Utility class - utility = createObject("component","wirebox.system.core.util.Util"); - - // Instance private scope - instance = structnew(); - - // CacheBox Provider Defaults - DEFAULTS = { - logBoxConfig = "wirebox.system.cache.config.LogBox", - cacheBoxProvider = "wirebox.system.cache.providers.CacheBoxProvider", - coldboxAppProvider = "wirebox.system.cache.providers.CacheBoxColdBoxProvider", - scopeRegistration = { - enabled = true, - scope = "application", - key = "cachebox" - } - }; - - // Startup the configuration - reset(); - - - - - - - - var cacheBoxDSL = ""; - - // Test and load via Data CFC Path - if( structKeyExists( arguments, "CFCConfigPath" ) ){ - arguments.CFCConfig = createObject( "component", arguments.CFCConfigPath ); - } - - // Test and load via Data CFC - if( structKeyExists( arguments, "CFCConfig" ) and isObject( arguments.CFCConfig ) ){ - // Decorate our data CFC - arguments.CFCConfig.getPropertyMixin = utility.getMixerUtil().getPropertyMixin; - // Execute the configuration - arguments.CFCConfig.configure(); - // Get Data - cacheBoxDSL = arguments.CFCConfig.getPropertyMixin("cacheBox","variables",structnew()); - // Load the DSL - loadDataDSL( cacheBoxDSL ); - } - - // Just return, most likely programmatic config - return this; - - - - - - - - - - - - - - - - - - - - - - - - - var cacheBoxDSL = arguments.rawDSL; - var key = ""; - - // Is default configuration defined - if( NOT structKeyExists( cacheBoxDSL, "defaultCache" ) ){ - throw("No default cache defined","Please define the 'defaultCache'","CacheBoxConfig.NoDefaultCacheFound"); - } - - // Register Default Cache - defaultCache(argumentCollection=cacheBoxDSL.defaultCache); - - // Register LogBox Configuration - logBoxConfig( variables.DEFAULTS.logBoxConfig ); - if( structKeyExists( cacheBoxDSL, "logBoxConfig") ){ - logBoxConfig(cacheBoxDSL.logBoxConfig); - } - - // Register Server Scope Registration - if( structKeyExists( cacheBoxDSL, "scopeRegistration") ){ - scopeRegistration(argumentCollection=cacheBoxDSL.scopeRegistration); - } - - // Register Caches - if( structKeyExists( cacheBoxDSL, "caches") ){ - for( key in cacheBoxDSL.caches ){ - cacheBoxDSL.caches[key].name = key; - cache(argumentCollection=cacheBoxDSL.caches[key]); - } +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * ---- + * This is a CacheBox configuration object. You can use it to configure a CacheBox variables. + **/ +component accessors="true"{ + + /** + * Get the static default configuration struct + */ + property name="DEFAULTS" type="struct"; + + /** + * The logbox config dsl this config object represents + */ + property name="logBoxConfig"; + + /** + * The default cache config + */ + property name="defaultCache" type="struct"; + + /** + * The caches configuration + */ + property name="caches" type="struct"; + + /** + * The scope configuration settings + */ + property name="scopeRegistration" type="struct"; + + /** + * The scope configuration settings + */ + property name="listeners" type="array"; + + // Utility class + variables.utility = new wirebox.system.core.util.Util(); + // CacheBox Provider Defaults STATIC + variables.DEFAULTS = { + logBoxConfig = "wirebox.system.cache.config.LogBox", + cacheBoxProvider = "wirebox.system.cache.providers.CacheBoxProvider", + coldboxAppProvider = "wirebox.system.cache.providers.CacheBoxColdBoxProvider", + scopeRegistration = { + enabled = true, + scope = "application", + key = "cachebox" + } + }; + // Startup the configuration + reset(); + + /** + * Constructor + * + * @CFCConfig The cacheBox Data Configuration CFC instance + * @CFCConfigPath The cacheBox Data Configuration CFC path to use + */ + function init( CFCConfig, string CFCConfigPath ){ + // Test and load via Data CFC Path + if( !isNull( arguments.CFCConfigPath ) ){ + arguments.CFCConfig = createObject( "component", arguments.CFCConfigPath ); + } + + // Test and load via Data CFC + if( !isNull( arguments.CFCConfig ) and isObject( arguments.CFCConfig ) ){ + // Decorate our data CFC + arguments.CFCConfig.getPropertyMixin = utility.getMixerUtil().getPropertyMixin; + // Execute the configuration + arguments.CFCConfig.configure(); + // Load the DSL + loadDataDSL( + arguments.CFCConfig.getPropertyMixin( "cacheBox", "variables", {} ) + ); + } + + // Just return, most likely programmatic config + return this; + } + + /** + * Set the logBox Configuration to use + * @config The configuration file to use + */ + CacheBoxConfig function logBoxConfig( required string config ){ + variables.logBoxConfig = arguments.config; + return this; + } + + /** + * Load a data configuration CFC data DSL + */ + CacheBoxConfig function loadDataDSL( required struct rawDSL ){ + var cacheBoxDSL = arguments.rawDSL; + + // Is default configuration defined + if( isNull( cacheBoxDSL.defaultCache ) ){ + throw( + "No default cache defined", + "Please define the 'defaultCache'", + "CacheBoxConfig.NoDefaultCacheFound" + ); + } + + // Register Default Cache + this.defaultCache( argumentCollection=cacheBoxDSL.defaultCache ); + + // Register LogBox Configuration + this.logBoxConfig( variables.DEFAULTS.logBoxConfig ); + if( !isNull( cacheBoxDSL.logBoxConfig ) ){ + this.logBoxConfig( cacheBoxDSL.logBoxConfig ); + } + + // Register Server Scope Registration + if( !isNull( cacheBoxDSL.scopeRegistration ) ){ + this.scopeRegistration( argumentCollection=cacheBoxDSL.scopeRegistration ); + } + + // Register Caches + if( !isNull( cacheBoxDSL.caches ) ){ + for( var key in cacheBoxDSL.caches ){ + cacheBoxDSL.caches[ key ].name = key; + this.cache( argumentCollection=cacheBoxDSL.caches[ key ] ); } + } - // Register listeners - if( structKeyExists( cacheBoxDSL, "listeners") ){ - for(key=1; key lte arrayLen(cacheBoxDSL.listeners); key++ ){ - listener(argumentCollection=cacheBoxDSL.listeners[key]); - } - } - - - - - - - // default cache - instance.defaultCache = {}; - // logBox File - instance.logBoxConfig = ""; - // Named Caches - instance.caches = {}; - // Listeners - instance.listeners = []; - // Scope Registration - instance.scopeRegistration = { - enabled = false, - scope = "server", - key = "cachebox" - }; - - - - - - - - - - - - - - - - - - - - - - - - - - - // Is the default cache defined - if( structIsEmpty(instance.defaultCache) ){ - throw(message="Invalid Configuration. No default cache defined",type="CacheBoxConfig.NoDefaultCacheFound"); + // Register listeners + if( !isNull( cacheBoxDSL.listeners ) ){ + for( var key in cacheBoxDSL.listeners ){ + this.listener( argumentCollection=key ); } - - - - - - - - - - instance.scopeRegistration.enabled = arguments.enabled; - instance.scopeRegistration.key = arguments.key; - instance.scopeRegistration.scope = arguments.scope; - - return this; - - - - - - - - - - - - - - - - - - - - - - var cacheConfig = getDefaultCache(); - - // Append all incoming arguments to configuration, just in case using non-default arguments, maybe for stores - structAppend(cacheConfig, arguments); - - // coldbox enabled context - if( structKeyExists(arguments,"coldboxEnabled") AND arguments.coldboxEnabled ){ - cacheConfig.provider = variables.DEFAULTS.coldboxAppProvider; - } - else{ - cacheConfig.provider = variables.DEFAULTS.cacheboxProvider; - } - - return this; - - - - - - - - - - - - - - - instance.caches[arguments.name] = { - provider = arguments.provider, - properties = arguments.properties - }; - return this; - - - - - - - - - - - - - - - - - - - - - - - - - - - // Name check? - if( NOT len(arguments.name) ){ - arguments.name = listLast(arguments.class,"."); - } - // add listener - arrayAppend(instance.listeners, arguments); - - return this; - - - - - - - - - + } + + return this; + } + + /** + * Reset the configuration + */ + CacheBoxConfig function reset(){ + // default cache + variables.defaultCache = {}; + // logBox File + variables.logBoxConfig = ""; + // Named Caches + variables.caches = {}; + // Listeners + variables.listeners = []; + // Scope Registration + variables.scopeRegistration = { + enabled = false, + scope = "server", + key = "cachebox" + }; - \ No newline at end of file + return this; + } + + /** + * Reset the default cache configurations + */ + CacheBoxConfig function resetDefaultCache(){ + variables.defaultCache = {}; + return this; + } + + /** + * Reset the set caches + */ + CacheBoxConfig function resetCaches(){ + variables.caches = {}; + return this; + } + + /** + * Reset the set listeners + */ + CacheBoxConfig function resetListeners(){ + variables.listeners = []; + return this; + } + + /** + * A quick snapshot of the state + */ + struct function getMemento(){ + return variables.filter( function( k, v ){ + return ( !isCustomFunction( v ) && !isObject( v ) ); + } ); + } + + /** + * Validates the configuration. If not valid, it will throw an appropriate exception. + * + * @throws CacheBoxConfig.NoDefaultCacheFound + */ + CacheBoxConfig function validate(){ + // Is the default cache defined + if( structIsEmpty( variables.defaultCache ) ){ + throw( + message = "Invalid Configuration. No default cache defined", + type = "CacheBoxConfig.NoDefaultCacheFound" + ); + } + return this; + } + + /** + * Define the cachebox factory scope registration + * @enabled Enable registration + * @scope The scope to register on, defaults to application scope + * @key The key to use in the scope, defaults to cachebox + + */ + function scopeRegistration( + boolean enabled=variables.DEFAULTS.scopeRegistration.enabled, + string scope=variables.DEFAULTS.scopeRegistration.scope, + string key=variables.DEFAULTS.scopeRegistration.key + ){ + variables.scopeRegistration.enabled = arguments.enabled; + variables.scopeRegistration.key = arguments.key; + variables.scopeRegistration.scope = arguments.scope; + + return this; + } + + /** + * Setup the default cache + * + * @objectDefaultTimeout The default object timeout in minutes + * @objectDefaultLastAccessTimeout The last access or idle timeout in minutes + * @reapFrequency The reaping frequency in minutes + * @maxObjects The max objects to store + * @freeMemoryPercentageThreshold Activate free ram thresholds + * @useLastAccessTimeouts use idle timeouts + * @evictionPolicy The eviction policy to use + * @evictCount How many objects to evict + * @objectStore The storage provider + * @coldboxEnabled Is this ColdBox enabled or standalone + */ + CacheBoxConfig function defaultCache( + numeric objectDefaultTimeout, + numeric objectDefaultLastAccessTimeout, + numeric reapFrequency, + numeric maxObjects, + numeric freeMemoryPercentageThreshold, + boolean useLastAccessTimeouts, + string evictionPolicy, + numeric evictCount, + string objectStore, + boolean coldboxEnabled + ){ + // Append all incoming arguments to configuration, just in case using non-default arguments, maybe for stores + var cacheConfig = getDefaultCache(); + structAppend( cacheConfig, arguments ); + + // coldbox enabled context + if( !isNull( arguments.coldboxEnabled ) AND arguments.coldboxEnabled ){ + cacheConfig.provider = variables.DEFAULTS.coldboxAppProvider; + } else { + cacheConfig.provider = variables.DEFAULTS.cacheboxProvider; + } + + return this; + } + + /** + * Add a new cache config + * + * @name The cache name + * @provider The cache provider class, defaults to: wirebox.system.cache.providers.CacheBoxProvider + * @properties The structure of properties for the cache + */ + function cache( + required name, + string provider=variables.DEFAULTS.cacheBoxProvider, + struct properties={} + ){ + variables.caches[ arguments.name ] = { + provider = arguments.provider, + properties = arguments.properties + }; + return this; + } + + /** + * Get a specifed cache definition + * + * @name The cache name + */ + struct function getCache( required name ){ + return variables.caches[ arguments.name ]; + } + + /** + * Check if a cache definition exists + * + * @name The cache name + */ + boolean function cacheExists( required name ){ + return variables.caches.keyExists( arguments.name ); + } + + /** + * Add a new listener configuration + * + * @class The class of the listener + * @properties The struct of properties for the listener + * @name The unique name of the listner + */ + CacheBoxConfig function listener( required class, struct properties={}, name="" ){ + // Name check? + if( NOT len( arguments.name ) ){ + arguments.name = listLast( arguments.class, "." ); + } + // add listener + arrayAppend( variables.listeners, arguments ); + + return this; + } + +} \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/config/samples/Sample.CacheBox.cfc b/src/cfml/system/wirebox/system/cache/config/samples/Sample.CacheBox.cfc index 7450d75a9..3248af059 100644 --- a/src/cfml/system/wirebox/system/cache/config/samples/Sample.CacheBox.cfc +++ b/src/cfml/system/wirebox/system/cache/config/samples/Sample.CacheBox.cfc @@ -1,21 +1,21 @@ -/******************************************************************************** -* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp -* www.ortussolutions.com -******************************************************************************** -* The default ColdBox CacheBox configuration object that is used when the cache factory is created by itself -**/ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * ---- + * The default ColdBox CacheBox configuration object that is used when the cache factory is created by itself + **/ component{ - + /** * Configure CacheBox, that's it! */ function configure(){ - + // The CacheBox configuration structure DSL cacheBox = { // LogBox Configuration file - logBoxConfig = "wirebox.system.cache.config.LogBoxConfig", - + logBoxConfig = "wirebox.system.cache.config.LogBoxConfig", + // Scope registration, automatically register the cachebox factory instance on any CF scope // By default it registeres itself on server scope scopeRegistration = { @@ -23,7 +23,7 @@ component{ scope = "server", // server, session key = "cacheBox" }, - + // The defaultCache has an implicit name "default" which is a reserved cache name // It also has a default provider of cachebox which cannot be changed. // All timeouts are in minutes @@ -38,7 +38,7 @@ component{ maxObjects = 200, objectStore = "ConcurrentSoftReferenceStore" }, - + // Register all the custom named caches you like here caches = { sampleCache1 = { @@ -61,13 +61,13 @@ component{ } } }, - + // Register all event listeners here, they are created in the specified order listeners = [ // { class="", name="", properties={} } ] - + }; - } + } } \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/policies/AbstractEvictionPolicy.cfc b/src/cfml/system/wirebox/system/cache/policies/AbstractEvictionPolicy.cfc index 379e1fb78..95a71c993 100644 --- a/src/cfml/system/wirebox/system/cache/policies/AbstractEvictionPolicy.cfc +++ b/src/cfml/system/wirebox/system/cache/policies/AbstractEvictionPolicy.cfc @@ -1,13 +1,13 @@ -/** +/** * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp * www.ortussolutions.com * ---- * This is an AbstractEviction Policy object for usage in a CacheBox provider -* +* * @doc_abstract true */ component serializable=false implements="wirebox.system.cache.policies.IEvictionPolicy" accessors="true"{ - + /** * A logbox logger */ @@ -21,19 +21,19 @@ component serializable=false implements="wirebox.system.cache.policies.IEviction /** * Constructor * @cacheProvider The associated cache provider - * @cacheProvider.doc_generic wirebox.system.cache.ICacheProvider + * @cacheProvider.doc_generic wirebox.system.cache.providers.ICacheProvider */ function init( required cacheProvider ){ // link associated cache variables.cacheProvider = arguments.cacheProvider; // setup logger variables.logger = arguments.cacheProvider.getCacheFactory().getLogBox().getLogger( this ); - + // Debug logging if( variables.logger.canDebug() ){ variables.logger.debug( "Policy #getMetadata( this ).name# constructed for cache: #arguments.cacheProvider.getname()#" ); } - + return this; } @@ -41,67 +41,64 @@ component serializable=false implements="wirebox.system.cache.policies.IEviction /** * Execute the eviction policy on the associated cache */ - public void function execute(){ - throw( "Abstract method!" ); + void function execute(){ + throw( "Abstract method!" ); } /** - * Get the Associated Cache Provider of type: wirebox.system.cache.ICacheProvider - * - * @return wirebox.system.cache.ICacheProvider + * Get the Associated Cache Provider of type: wirebox.system.cache.providers.ICacheProvider + * + * @return wirebox.system.cache.providers.ICacheProvider */ - public any function getAssociatedCache(){ + any function getAssociatedCache(){ return variables.cacheProvider; } /****************************************** PRIVATE ************************************************/ /** - * Abstract processing of evictions - * @index The array of metadata keys used for processing evictions - */ - private function processEvictions( required any index ){ - var oCacheManager = variables.cacheProvider; - var indexer = oCacheManager.getObjectStore().getIndexer(); - var indexLength = arrayLen(arguments.index); - var x = 1; - var md = ""; - var evictCount = oCacheManager.getConfiguration().evictCount; + * Abstract processing of evictions + * + * @index The array of metadata keys used for processing evictions + */ + private function processEvictions( required array index ){ + var indexer = variables.cacheProvider.getObjectStore().getIndexer(); + var evictCount = variables.cacheProvider.getConfiguration().evictCount; var evictedCounter = 0; - - //Loop Through Metadata - for (x=1; x lte indexLength; x=x+1){ - + + // TODO: Move to streams : Loop Through Metadata + for ( var item in arguments.index ){ + // verify object in indexer - if( NOT indexer.objectExists( arguments.index[x] ) ){ + if( NOT indexer.objectExists( item ) ){ continue; } - md = indexer.getObjectMetadata( arguments.index[x] ); - + var md = indexer.getObjectMetadata( item ); + // Evict if not already marked for eviction or an eternal object. - if( md.timeout gt 0 AND NOT md.isExpired ){ - + if( md.timeout GT 0 AND NOT md.isExpired ){ + // Evict The Object - oCacheManager.clear( arguments.index[x] ); - - // Record Eviction - oCacheManager.getStats().evictionHit(); + variables.cacheProvider.clear( item ); + + // Record Eviction + variables.cacheProvider.getStats().evictionHit(); evictedCounter++; - + // Can we break or keep on evicting if( evictedCounter GTE evictCount ){ break; - } + } } }//end for loop } - + /** * Get utiliy object * @return wirebox.system.core.util.Util */ private function getUtil(){ - return new wirebox.system.core.util.Util(); + return new wirebox.system.core.util.Util(); } } \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/policies/FIFO.cfc b/src/cfml/system/wirebox/system/cache/policies/FIFO.cfc index 18fdc4f5b..c00ef9c1f 100644 --- a/src/cfml/system/wirebox/system/cache/policies/FIFO.cfc +++ b/src/cfml/system/wirebox/system/cache/policies/FIFO.cfc @@ -1,4 +1,4 @@ -/** +/** * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp * www.ortussolutions.com * ---- @@ -10,33 +10,32 @@ */ component extends="wirebox.system.cache.policies.AbstractEvictionPolicy"{ - /** - * Constructor - * @cacheProvider The associated cache provider of type: wirebox.system.cache.ICacheProvider" doc_generic="wirebox.system.cache.ICacheProvider - */ - public FIFO function init ( required any cacheProvider ){ + /** + * Constructor + * + * @cacheProvider The associated cache provider of type: wirebox.system.cache.providers.ICacheProvider" doc_generic="wirebox.system.cache.providers.ICacheProvider + */ + FIFO function init ( required any cacheProvider ){ super.init( arguments.cacheProvider ); return this; } /** - * Execute the policy - */ - public void function execute (){ - var index = ""; - + * Execute the policy + */ + void function execute (){ // Get searchable index try{ - index = getAssociatedCache() + var index = getAssociatedCache() .getObjectStore() .getIndexer() .getSortedKeys( "created", "numeric", "asc" ); // process evictions processEvictions( index ); - } catch(Any e) { + } catch( Any e ) { getLogger().error( "Error sorting via store indexer #e.message# #e.detail# #e.stackTrace#." ); - } + } } } \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/policies/IEvictionPolicy.cfc b/src/cfml/system/wirebox/system/cache/policies/IEvictionPolicy.cfc index 13f745de1..766d8a75d 100644 --- a/src/cfml/system/wirebox/system/cache/policies/IEvictionPolicy.cfc +++ b/src/cfml/system/wirebox/system/cache/policies/IEvictionPolicy.cfc @@ -4,19 +4,19 @@ * ---- * * CacheBox Eviction polify interface -*/ +*/ interface{ - + /** * Execute the eviction policy on the associated cache */ - public void function execute(); + void function execute(); /** - * Get the Associated Cache Provider of type: wirebox.system.cache.ICacheProvider - * - * @return wirebox.system.cache.ICacheProvider + * Get the Associated Cache Provider of type: wirebox.system.cache.providers.ICacheProvider + * + * @return wirebox.system.cache.providers.ICacheProvider */ - public any function getAssociatedCache(); + any function getAssociatedCache(); } \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/policies/LFU.cfc b/src/cfml/system/wirebox/system/cache/policies/LFU.cfc index 5f64e9928..3b0a6645c 100644 --- a/src/cfml/system/wirebox/system/cache/policies/LFU.cfc +++ b/src/cfml/system/wirebox/system/cache/policies/LFU.cfc @@ -12,9 +12,9 @@ component extends = "wirebox.system.cache.policies.AbstractEvictionPolicy"{ /** * Constructor - * @cacheProvider The associated cache provider of type: wirebox.system.cache.ICacheProvider" doc_generic="wirebox.system.cache.ICacheProvider + * @cacheProvider The associated cache provider of type: wirebox.system.cache.providers.ICacheProvider" doc_generic="wirebox.system.cache.providers.ICacheProvider */ - public LFU function init( required any cacheProvider ){ + LFU function init( required any cacheProvider ){ super.init( arguments.cacheProvider ); return this; @@ -23,12 +23,10 @@ component extends = "wirebox.system.cache.policies.AbstractEvictionPolicy"{ /** * Execute the policy */ - public void function execute(){ - var index = ""; - + void function execute(){ // Get searchable index try { - index = getAssociatedCache() + var index = getAssociatedCache() .getObjectStore() .getIndexer() .getSortedKeys( "hits", "numeric", "asc" ); diff --git a/src/cfml/system/wirebox/system/cache/policies/LIFO.cfc b/src/cfml/system/wirebox/system/cache/policies/LIFO.cfc index a63c52cf2..32df346bf 100644 --- a/src/cfml/system/wirebox/system/cache/policies/LIFO.cfc +++ b/src/cfml/system/wirebox/system/cache/policies/LIFO.cfc @@ -10,28 +10,26 @@ * * More information can be found here: * http://en.wikipedia.org/wiki/FIFO -*/ +*/ component extends="wirebox.system.cache.policies.AbstractEvictionPolicy"{ - /** + /** * This is the constructor - * @cacheProvider The associated cache provider of type: wirebox.system.cache.ICacheProvider" doc_generic="wirebox.system.cache.ICacheProvider + * @cacheProvider The associated cache provider of type: wirebox.system.cache.providers.ICacheProvider" doc_generic="wirebox.system.cache.providers.ICacheProvider */ - public LIFO function init( required any cacheProvider ){ + LIFO function init( required any cacheProvider ){ super.init( arguments.cacheProvider ); - + return this; } - /** + /** * Execute the policy */ - public void function execute(){ - var index = ""; - + void function execute(){ // Get searchable index try{ - index = getAssociatedCache() + var index = getAssociatedCache() .getObjectStore() .getIndexer() .getSortedKeys( "Created", "numeric", "desc" ); @@ -39,7 +37,7 @@ component extends="wirebox.system.cache.policies.AbstractEvictionPolicy"{ processEvictions( index ); } catch( Any e ) { getLogger().error( "Error sorting via store indexer #e.message# #e.detail# #e.stackTrace#." ); - } + } } } \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/policies/LRU.cfc b/src/cfml/system/wirebox/system/cache/policies/LRU.cfc index bdce1ba61..0ea598b04 100644 --- a/src/cfml/system/wirebox/system/cache/policies/LRU.cfc +++ b/src/cfml/system/wirebox/system/cache/policies/LRU.cfc @@ -6,30 +6,28 @@ * This is the LRU or least recently used algorithm for cachebox. * It basically discards the least recently used items first according to the last accessed date. * This is also the default algorithm for CacheBox. -* +* * For more information visit: http://en.wikipedia.org/wiki/Least_Recently_Used -*/ +*/ component extends="wirebox.system.cache.policies.AbstractEvictionPolicy"{ - /** + /** * This is the constructor - * @cacheProvider The associated cache provider of type: wirebox.system.cache.ICacheProvider" doc_generic="wirebox.system.cache.ICacheProvider + * @cacheProvider The associated cache provider of type: wirebox.system.cache.providers.ICacheProvider" doc_generic="wirebox.system.cache.providers.ICacheProvider */ - public LRU function init( required any cacheProvider ){ + LRU function init( required any cacheProvider ){ super.init( arguments.cacheProvider ); - + return this; } - /** + /** * Execute the policy */ - public void function execute(){ - var index = ""; - + void function execute(){ // Get searchable index try{ - index = getAssociatedCache() + var index = getAssociatedCache() .getObjectStore() .getIndexer() .getSortedKeys( "LastAccessed", "numeric", "asc" ); @@ -37,7 +35,7 @@ component extends="wirebox.system.cache.policies.AbstractEvictionPolicy"{ processEvictions( index ); } catch( Any e ) { getLogger().error( "Error sorting via store indexer #e.message# #e.detail# #e.stackTrace#." ); - } + } } } \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/providers/CFColdBoxProvider.cfc b/src/cfml/system/wirebox/system/cache/providers/CFColdBoxProvider.cfc index e278f45bb..e2803378b 100644 --- a/src/cfml/system/wirebox/system/cache/providers/CFColdBoxProvider.cfc +++ b/src/cfml/system/wirebox/system/cache/providers/CFColdBoxProvider.cfc @@ -1,105 +1,171 @@ /** -******************************************************************************** -Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp -www.ortussolutions.com -******************************************************************************** -Author: Luis Majano -Description: - -This CacheBox provider communicates with the built in caches in -the Adobe ColdFusion Engine for ColdBox applications. - -*/ -component serializable="false" extends="wirebox.system.cache.providers.CFProvider" implements="wirebox.system.cache.IColdboxApplicationCache"{ + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * @author Luis Majano + * + * This CacheBox provider communicates with the built in caches in the Adobe Engine for ColdBox Apps + */ +component + accessors="true" + serializable="false" + implements="wirebox.system.cache.providers.IColdBoxProvider" + extends="wirebox.system.cache.providers.CFProvider" +{ + // Cache Prefixes + this.VIEW_CACHEKEY_PREFIX = "cf_view-"; + this.EVENT_CACHEKEY_PREFIX = "cf_event-"; + + /** + * Constructor + */ CFColdBoxProvider function init() output=false{ super.init(); - - // Prefixes - this.VIEW_CACHEKEY_PREFIX = "cf_view-"; - this.EVENT_CACHEKEY_PREFIX = "cf_event-"; - + // URL Facade Utility - instance.eventURLFacade = CreateObject("component","wirebox.system.cache.util.EventURLFacade").init(this); - + variables.eventURLFacade = new wirebox.system.cache.util.EventURLFacade( this ); + return this; } - - // Cache Key prefixes - any function getViewCacheKeyPrefix() output=false{ return this.VIEW_CACHEKEY_PREFIX; } - any function getEventCacheKeyPrefix() output=false{ return this.EVENT_CACHEKEY_PREFIX; } - - // set the coldbox controller - void function setColdbox(required any coldbox) output=false{ + + /** + * Get the cached view key prefix which is necessary for view caching + */ + function getViewCacheKeyPrefix(){ + return this.VIEW_CACHEKEY_PREFIX; + }; + + /** + * Get the event cache key prefix which is necessary for event caching + */ + function getEventCacheKeyPrefix(){ + return this.EVENT_CACHEKEY_PREFIX; + } + + /** + * Get the coldbox application reference as wirebox.system.web.Controller + * + * @return wirebox.system.web.Controller + */ + function getColdbox(){ + return variables.coldbox; + } + + /** + * Set the ColdBox linkage into the provider + * + * @coldbox The ColdBox controller + * @coldbox.doc_generic wirebox.system.web.Controller + * + * @return IColdboxApplicationCache + */ + function setColdBox( required coldbox ){ variables.coldbox = arguments.coldbox; + return this; } - - // Get ColdBox - any function getColdbox() output=false{ return coldbox; } - - // Get Event URL Facade Tool - any function getEventURLFacade() output=false{ return instance.eventURLFacade; } - + + /** + * Get the event caching URL facade utility that determines event caching + * + * @return wirebox.system.cache.util.EventURLFacade + */ + function getEventURLFacade(){ + return variables.eventURLFacade; + } + /** - * Clear all events - */ - void function clearAllEvents(async=false) output=false{ - var threadName = "clearAllEvents_#replace(instance.uuidHelper.randomUUID(),"-","","all")#"; - + * Clears all events from the cache. + * + * @async If implemented, determines async or sync clearing. + * + * @return IColdboxApplicationCache + */ + function clearAllEvents( boolean async=false ){ + var threadName = "clearAllEvents_#replace(variables.uuidHelper.randomUUID(),"-","","all")#"; + // Async? IF so, do checks - if( arguments.async AND NOT instance.utility.inThread() ){ + if( arguments.async AND NOT variables.utility.inThread() ){ thread name="#threadName#"{ - instance.elementCleaner.clearAllEvents(); + variables.elementCleaner.clearAllEvents(); } } else{ - instance.elementCleaner.clearAllEvents(); - } + variables.elementCleaner.clearAllEvents(); + } + return this; + } + + /** + * Clears all the event permutations from the cache according to snippet and querystring. Be careful when using incomplete event name with query strings as partial event names are not guaranteed to match with query string permutations + * + * @eventSnippet The event snippet to clear on. Can be partial or full + * @queryString If passed in, it will create a unique hash out of it. For purging purposes + * + * @return IColdboxApplicationCache + */ + function clearEvent( required eventSnippet, queryString="" ){ + variables.elementCleaner.clearEvent( arguments.eventsnippet, arguments.queryString ); + return this; } - + /** - * Clear all views - */ - void function clearAllViews(async=false) output=false{ - var threadName = "clearAllViews_#replace(instance.uuidHelper.randomUUID(),"-","","all")#"; - + * Clears all views from the cache. + * + * @async Run command asynchronously or not + * + * @return IColdboxApplicationCache + */ + function clearAllViews( boolean async=false ){ + var threadName = "clearAllViews_#replace(variables.uuidHelper.randomUUID(),"-","","all")#"; + // Async? IF so, do checks - if( arguments.async AND NOT instance.utility.inThread() ){ + if( arguments.async AND NOT variables.utility.inThread() ){ thread name="#threadName#"{ - instance.elementCleaner.clearAllViews(); + variables.elementCleaner.clearAllViews(); } } else{ - instance.elementCleaner.clearAllViews(); + variables.elementCleaner.clearAllViews(); } + return this; } - - /** - * Clear event - */ - void function clearEvent(required eventsnippet, queryString="") output=false{ - instance.elementCleaner.clearEvent(arguments.eventsnippet,arguments.queryString); - } - + /** - * Clear multiple events - */ - void function clearEventMulti(required eventsnippets,queryString="") output=false{ - instance.elementCleaner.clearEventMulti(arguments.eventsnippets,arguments.queryString); + * Clears all the event permutations from the cache according to the list of snippets and querystrings. Be careful when using incomplete event name with query strings as partial event names are not guaranteed to match with query string permutations + * + * @eventSnippet The comma-delimmitted list event snippet to clear on. Can be partial or full + * @queryString The comma-delimmitted list of queryStrings passed in. If passed in, it will create a unique hash out of it. For purging purposes. If passed in the list length must be equal to the list length of the event snippets passed in + * + * @return IColdboxApplicationCache + */ + function clearEventMulti( required eventsnippets, queryString="" ){ + variables.elementCleaner.clearEventMulti(arguments.eventsnippets,arguments.queryString); + return this; } - + /** - * Clear view - */ - void function clearView(required viewSnippet) output=false{ - instance.elementCleaner.clearView(arguments.viewSnippet); + * Clears all view name permutations from the cache according to the view name + * + * @viewSnippet The view name snippet to purge from the cache + * + * @return IColdboxApplicationCache + */ + function clearView( required viewSnippet ){ + variables.elementCleaner.clearView(arguments.viewSnippet); + return this; } - + /** - * Clear multiple view - */ - void function clearViewMulti(required viewsnippets) output=false{ - instance.elementCleaner.clearView(arguments.viewsnippets); + * Clears all view name permutations from the cache according to the view name. + * + * @viewSnippets The comma-delimmitted list or array of view snippet to clear on. Can be partial or full + * + * @return IColdboxApplicationCache + */ + function clearViewMulti( required viewSnippets ){ + variables.elementCleaner.clearView(arguments.viewsnippets); + return this; } - + } \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/providers/CFProvider.cfc b/src/cfml/system/wirebox/system/cache/providers/CFProvider.cfc index d480b8f32..de531ee97 100644 --- a/src/cfml/system/wirebox/system/cache/providers/CFProvider.cfc +++ b/src/cfml/system/wirebox/system/cache/providers/CFProvider.cfc @@ -1,147 +1,70 @@ /** -******************************************************************************** -Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp -www.ortussolutions.com -******************************************************************************** -Author: Luis Majano -Description: - -This CacheBox provider communicates with the built in caches in -the Adobe ColdFusion Engine. - -*/ -component serializable="false" implements="wirebox.system.cache.ICacheProvider"{ + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * @author Luis Majano + * + * This CacheBox provider communicates with the built in caches in the Adobe ColdFusion Engines + */ +component + accessors="true" + serializable="false" + implements="wirebox.system.cache.providers.ICacheProvider" + extends="wirebox.system.cache.AbstractCacheBoxProvider" +{ /** - * Constructor - */ - CFProvider function init() output=false{ - // Setup Cache instance - instance = { - // cache name - name = "", - // enabled cache flag - enabled = false, - // reporting enabled flag - reportingEnabled = false, - // configuration structure - configuration = {}, - // cache factory reference - cacheFactory = "", - // event manager reference - eventManager = "", - // reference to underlying data store - store = "", - // internal cache id - cacheID = createObject('java','java.lang.System').identityHashCode(this), - // Element Cleaner Helper - elementCleaner = CreateObject("component","wirebox.system.cache.util.ElementCleaner").init(this), - // Utilities - utility = createObject("component","wirebox.system.core.util.Util"), - // uuid creation helper - uuidHelper = createobject("java", "java.util.UUID") - }; - - // Provider Property Defaults - instance.DEFAULTS = { - cacheName = "object" - }; - - return this; - } - - /** - * get the cache name - */ - any function getName() output=false{ - return instance.name; - } - - /** - * set the cache name - */ - void function setName(required name) output=false{ - instance.name = arguments.name; - } - - /** - * set the event manager - */ - void function setEventManager(required any EventManager) output=false{ - instance.eventManager = arguments.eventManager; - } - - /** - * get the event manager - */ - any function getEventManager() output=false{ - return instance.eventManager; - } - - /** - * get the cache configuration structure - */ - any function getConfiguration() output=false{ - return instance.configuration; - } - - /** - * set the cache configuration structure - */ - void function setConfiguration(required configuration) output=false{ - instance.configuration = arguments.configuration; - } - - /** - * get the associated cache factory - */ - any function getCacheFactory() output=false{ - return instance.cacheFactory; - } - + * The global element cleaner utility object + */ + property name="elementCleaner"; + + // Provider Property Defaults STATIC + variables.DEFAULTS = { + cacheName = "object" + }; + /** - * Validate the configuration - **/ - private void function validateConfiguration(){ - var cacheConfig = getConfiguration(); - var key = ""; - - // Validate configuration values, if they don't exist, then default them to DEFAULTS - for(key in instance.DEFAULTS){ - if( NOT structKeyExists(cacheConfig, key) OR NOT len(cacheConfig[key]) ){ - cacheConfig[key] = instance.DEFAULTS[key]; - } - } + * Constructor + */ + function init(){ + super.init(); + + // Element Cleaner Helper + variables.elementCleaner = new wirebox.system.cache.util.ElementCleaner( this ); + + return this; } - + /** - * configure the cache for operation - */ - void function configure() output=false{ + * configure the cache for operation + * + * @return LuceeProvider + */ + function configure(){ var config = getConfiguration(); var props = []; - lock name="CFProvider.config.#instance.cacheID#" type="exclusive" throwontimeout="true" timeout="20"{ - + lock name="CFProvider.config.#variables.cacheID#" type="exclusive" throwontimeout="true" timeout="30"{ + // Prepare the logger - instance.logger = getCacheFactory().getLogBox().getLogger( this ); - - if( instance.logger.canDebug() ) - instance.logger.debug("Starting up CFProvider Cache: #getName()# with configuration: #config.toString()#"); - + variables.logger = getCacheFactory().getLogBox().getLogger( this ); + + if( variables.logger.canDebug() ){ + variables.logger.debug( "Starting up CFProvider Cache: #getName()# with configuration: #config.toString()#" ); + } + // Validate the configuration validateConfiguration(); - // Merge configurations + // Merge configurations var thisCacheName = config.cacheName; if ( thisCacheName == "object") { props = cacheGetProperties(); - } - else { - + } else { + // this force CF to create the user defined cache if it doesn't exist get("___invalid___"); - + var cacheConfig = cacheGetSession( thisCacheName, true ).getCacheConfiguration(); // apply parameter configurations @@ -150,206 +73,220 @@ component serializable="false" implements="wirebox.system.cache.ICacheProvider"{ } if ( structKeyExists( config, "diskExpiryThreadIntervalSeconds") ) { cacheConfig.setDiskExpiryThreadIntervalSeconds( config.diskExpiryThreadIntervalSeconds ); - } + } if ( structKeyExists( config, "diskPersistent") ) { cacheConfig.setDiskPersistent( config.diskPersistent ); - } + } if ( structKeyExists( config, "diskSpoolBufferSizeMB") ) { cacheConfig.setDiskSpoolBufferSizeMB( config.diskSpoolBufferSizeMB ); - } + } if ( structKeyExists( config, "eternal") ) { cacheConfig.setEternal( config.eternal ); - } + } if ( structKeyExists( config, "maxElementsInMemory") ) { cacheConfig.setMaxElementsInMemory( config.maxElementsInMemory ); - } + } if ( structKeyExists( config, "maxElementsOnDisk") ) { cacheConfig.setMaxElementsOnDisk( config.maxElementsOnDisk ); - } + } if ( structKeyExists( config, "memoryEvictionPolicy") ) { cacheConfig.setMemoryStoreEvictionPolicy( config.memoryEvictionPolicy ); } if ( structKeyExists( config, "overflowToDisk") ) { cacheConfig.setOverflowToDisk( config.overflowToDisk ); - } + } if ( structKeyExists( config, "timeToIdleSeconds") ) { cacheConfig.setTimeToIdleSeconds( config.timeToIdleSeconds ); - } + } if ( structKeyExists( config, "timeToLiveSeconds") ) { cacheConfig.setTimeToLiveSeconds( config.timeToLiveSeconds ); - } - + } + props = [{ "objectType" = config.cacheName , "clearOnFlush" = cacheConfig.isClearOnFlush() - , "diskExpiryThreadIntervalSeconds" = cacheConfig.getDiskExpiryThreadIntervalSeconds() + , "diskExpiryThreadIntervalSeconds" = cacheConfig.getDiskExpiryThreadIntervalSeconds() , "diskPersistent" = cacheConfig.isDiskPersistent() , "diskSpoolBufferSizeMB" = cacheConfig.getDiskSpoolBufferSizeMB() , "eternal" = cacheConfig.isEternal() , "maxElementsInMemory" = cacheConfig.getMaxElementsInMemory() - , "maxElementsOnDisk" = cacheConfig.getMaxElementsOnDisk() + , "maxElementsOnDisk" = cacheConfig.getMaxElementsOnDisk() , "memoryEvictionPolicy" = cacheConfig.getMemoryStoreEvictionPolicy().toString() , "overflowToDisk" = cacheConfig.isOverflowToDisk() , "timeToIdleSeconds" = cacheConfig.getTimeToIdleSeconds() , "timeToLiveSeconds" = cacheConfig.getTimeToLiveSeconds() }]; } - - var key = ""; - for(key in props){ - config["ehcache_#key.objectType#"] = key; + + for( var key in props ){ + config[ "ehcache_#key.objectType#" ] = key; } - + // enabled cache - instance.enabled = true; - instance.reportingEnabled = true; - - if( instance.logger.canDebug() ) - instance.logger.debug( "Cache #getName()# started up successfully" ); + variables.enabled = true; + variables.reportingEnabled = true; + + if( variables.logger.canDebug() ){ + variables.logger.debug( "Cache #getName()# started up successfully" ); + } } + + return this; } - + /** - * shutdown the cache - */ - void function shutdown() output=false{ - if( instance.logger.canDebug() ) - instance.logger.debug( "CFProvider Cache: #getName()# has been shutdown." ); + * Shutdown command issued when CacheBox is going through shutdown phase + * + * @return LuceeProvider + */ + function shutdown(){ + //nothing to shutdown + if( variables.logger.canDebug() ){ + variables.logger.debug( "CFProvider Cache: #getName()# has been shutdown." ); + } + return this; } - - /* - * Indicates if cache is ready for operation - */ - any function isEnabled() output=false{ - return instance.enabled; - } /* - * Indicates if cache is ready for operation - */ - any function isReportingEnabled() output=false{ - return instance.reportingEnabled; - } - - /* - * Indicates if the cache is Terracota clustered - */ - any function isTerracotaClustered(){ + * Indicates if the cache is Terracota clustered + */ + boolean function isTerracotaClustered(){ return getObjectStore().isTerracottaClustered(); } - + /* - * Indicates if the cache node is coherent - */ - any function isNodeCoherent(){ + * Indicates if the cache node is coherent + */ + boolean function isNodeCoherent(){ return getObjectStore().isNodeCoherent(); } - + /* - * Returns true if the cache is in coherent mode cluster-wide. - */ - any function isClusterCoherent(){ + * Returns true if the cache is in coherent mode cluster-wide. + */ + boolean function isClusterCoherent(){ return getObjectStore().isClusterCoherent(); } - - /* - * Get the cache statistics object as wirebox.system.cache.util.ICacheStats - * @doc_generic wirebox.system.cache.util.ICacheStats - */ - any function getStats() output=false{ - return CreateObject("component", "wirebox.system.cache.providers.cf-lib.CFStats").init( getObjectStore().getStatistics() ); + + /** + * Get the cache statistics object as wirebox.system.cache.util.IStats + * + * @return wirebox.system.cache.util.IStats + */ + function getStats(){ + return new "wirebox.system.cache.providers.cf-lib.CFStats"( getObjectStore().getStatistics() ); } - + /** - * clear the cache stats - */ - void function clearStatistics() output=false{ - getObjectStore().clearStatistics(); + * Clear the cache statistics + * NOT IMPLEMENTED FOR ACF 2016+ + * + * @return ICacheProvider + */ + function clearStatistics(){ + if( server.coldfusion.productVersion.listFirst() == 11 ){ + getObjectStore().clearStatistics(); + } else { + // New version of ehcache removed this feature. + } + + return this; } - + /** - * Returns the ehCache storage session according to configured cache name - */ - any function getObjectStore() output=false{ + * If the cache provider implements it, this returns the cache's object store. + * + * @return wirebox.system.cache.store.IObjectStore or any depending on the cache implementation + */ + function getObjectStore(){ // get the cache session according to set name var thisCacheName = getConfiguration().cacheName; - if ( thisCacheName == "object") { + if( thisCacheName == "object"){ return cacheGetSession( "object" ); } else { - return cacheGetSession( getConfiguration().cacheName, true ); + return cacheGetSession( thisCacheName, true ); } } - + /** - * get the cache's metadata report - */ - any function getStoreMetadataReport() output=false{ - var md = {}; - var keys = getKeys(); - var item = ""; - - for(item in keys){ - md[item] = getCachedObjectMetadata(item); - } - - return md; + * Get a structure of all the keys in the cache with their appropriate metadata structures. This is used to build the reporting.[keyX->[metadataStructure]] + */ + struct function getStoreMetadataReport(){ + return getKeys() + .reduce( function( item, result ){ + result[ item ] = getCachedObjectMetadata( item ); + return result; + }, {} ); } - + /** - * Get a key lookup structure where cachebox can build the report on. Ex: [timeout=timeout,lastAccessTimeout=idleTimeout]. It is a way for the visualizer to construct the columns correctly on the reports - */ - any function getStoreMetadataKeyMap() output="false"{ - var keyMap = { - timeout = "timespan", hits = "hitcount", lastAccessTimeout = "idleTime", - created = "createdtime", LastAccessed = "lasthit" - }; - return keymap; + * Get a key lookup structure where cachebox can build the report on. Ex: [timeout=timeout,lastAccessTimeout=idleTimeout]. It is a way for the visualizer to construct the columns correctly on the reports + */ + struct function getStoreMetadataKeyMap(){ + return { + timeout = "timespan", + hits = "hitcount", + lastAccessTimeout = "idleTime", + created = "createdtime", + lastAccessed = "lasthit" + }; } - + /** - * get all the keys in this provider - */ - any function getKeys() output=false{ + * Returns a list of all elements in the cache, whether or not they are expired + */ + array function getKeys(){ try{ var thisCacheName = getConfiguration().cacheName; if ( thisCacheName == "object") { return cacheGetAllIds(); } return cacheGetAllIds( thisCacheName ); - } - catch(Any e){ - instance.logger.error( "Error retrieving all keys from cache: #e.message# #e.detail#", e.stacktrace ); + } catch( Any e ) { + variables.logger.error( "Error retrieving all keys from cache: #e.message# #e.detail#", e.stacktrace ); return [ "Error retrieving keys from cache: #e.message#" ]; } } - + /** - * get an object's cached metadata - */ - any function getCachedObjectMetadata(required any objectKey) output=false{ + * Get a cache objects metadata about its performance. This value is a structure of name-value pairs of metadata. + * + * @objectKey The key to retrieve + */ + struct function getCachedObjectMetadata( required objectKey ){ var thisCacheName = getConfiguration().cacheName; - if ( thisCacheName == "object") { + if ( thisCacheName == "object") { return cacheGetMetadata( arguments.objectKey ); } else { - return; + return cacheGetMetadata( arguments.objectName, thisCacheName ); } } - + /** - * get an item from cache - */ - any function get(required any objectKey) output=false{ - var thisCacheName = getConfiguration().cacheName; + * Get an object from the cache + * + * @objectKey The key to retrieve + */ + function get( required objectKey ){ + var thisCacheName = getConfiguration().cacheName; if ( thisCacheName == "object") { - return cacheGet( arguments.objectKey ); - } else { - return cacheGet( arguments.objectKey, thisCacheName ); - } + return cacheGet( arguments.objectKey ); + } else { + return cacheGet( arguments.objectKey, thisCacheName ); + } } - + /** - * Tries to get an object from the cache, if not found, it calls the 'produce' closure to produce the data and cache it - */ + * Tries to get an object from the cache, if not found, it calls the 'produce' closure to produce the data and cache it + * + * @objectKey The object cache key + * @produce The producer closure/lambda + * @timeout The timeout to use on the object (if any, provider specific) + * @lastAccessTimeout The idle timeout to use on the object (if any, provider specific) + * @extra A map of name-value pairs to use as extra arguments to pass to a providers set operation + * + * @return The cached or produced data/object + */ any function getOrSet( required any objectKey, required any produce, @@ -357,106 +294,112 @@ component serializable="false" implements="wirebox.system.cache.ICacheProvider"{ any lastAccessTimeout="0", any extra={} ){ - - var refLocal = { - object = get( arguments.objectKey ) - }; - - // Verify if it exists? if so, return it. - if( structKeyExists( refLocal, "object" ) ){ return refLocal.object; } - - // else, produce it - lock name="CacheBoxProvider.GetOrSet.#instance.cacheID#.#arguments.objectKey#" type="exclusive" timeout="10" throwonTimeout="true"{ - // double lock - refLocal.object = get( arguments.objectKey ); - if( not structKeyExists( refLocal, "object" ) ){ - // produce it - refLocal.object = arguments.produce(); - // store it - set( objectKey=arguments.objectKey, - object=refLocal.object, - timeout=arguments.timeout, - lastAccessTimeout=arguments.lastAccessTimeout, - extra=arguments.extra ); - } - } - - return refLocal.object; + return super.getOrSet( argumentCollection=arguments ); } - + /** - * get an item silently from cache, no stats advised - */ - any function getQuiet(required any objectKey) output=false{ - var element = getObjectStore().getQuiet( ucase(arguments.objectKey) ); - if( NOT isNull(element) ){ + * get an item silently from cache, no stats advised: Stats not available on lucee + * + * @objectKey The key to retrieve + */ + function getQuiet( required objectKey ){ + + if( server.coldfusion.productVersion.listFirst() == 2018 ){ + var element = getObjectStore().getQuiet( arguments.objectKey ); + } else { + var element = getObjectStore().getQuiet( ucase( arguments.objectKey ) ); + } + + if( !isNull( local.element ) ){ return element.getValue(); } } - + /** - * Not implemented by this cache - */ - any function isExpired(required any objectKey) output=false{ - var element = getObjectStore().getQuiet( ucase(arguments.objectKey) ); - if( NOT isNull(element) ){ - return element.isExpired(); - } - return true; + * Has the object key expired in the cache: NOT IMPLEMENTED IN THIS CACHE + * + * @objectKey The key to retrieve + */ + boolean function isExpired( required objectKey ){ + return false; } - + /** - * check if object in cache - */ - any function lookup(required any objectKey) output=false{ - return lookupQuiet(arguments.objectKey); + * Check if an object is in cache, if not found it records a miss. + * + * @objectKey The key to retrieve + */ + boolean function lookup( required objectKey ){ + return lookupQuiet( arguments.objectKey ); } - + /** - * check if object in cache with no stats - */ - any function lookupQuiet(required any objectKey) output=false{ - var thisCacheName = getConfiguration().cacheName; + * Check if an object is in cache, no stats updated or listeners + * + * @objectKey The key to retrieve + */ + boolean function lookupQuiet( required objectKey ){ + var thisCacheName = getConfiguration().cacheName; if ( thisCacheName == "object") { - return !isNull ( cacheGet( arguments.objectKey ) ); - } else { - return !isNull ( cacheGet( arguments.objectKey, thisCacheName ) ); - } + return cacheIdExists( arguments.objectKey ); + } else { + return cacheIdExists( arguments.objectKey, thisCacheName ); + } } - + /** - * set an object in cache - */ - any function set(required any objectKey, - required any object, - any timeout="0", - any lastAccessTimeout="0", - any extra) output=false{ - - setQuiet(argumentCollection=arguments); - + * Sets an object in the cache and returns an instance of itself + * + * @objectKey The object cache key + * @object The object to cache + * @timeout The timeout to use on the object (if any, provider specific) + * @lastAccessTimeout The idle timeout to use on the object (if any, provider specific) + * @extra A map of name-value pairs to use as extra arguments to pass to a providers set operation + * + * @return ICacheProvider + */ + function set( + required objectKey, + required object, + timeout=0, + lastAccessTimeout=0, + struct extra + ){ + + setQuiet( argumentCollection=arguments ); + //ColdBox events - var iData = { + var iData = { cache = this, cacheObject = arguments.object, cacheObjectKey = arguments.objectKey, cacheObjectTimeout = arguments.timeout, cacheObjectLastAccessTimeout = arguments.lastAccessTimeout - }; - getEventManager().processState("afterCacheElementInsert",iData); - - return true; - } - + }; + getEventManager().processState( "afterCacheElementInsert", iData ); + + return this; + } + /** - * set an object in cache with no stats - */ - any function setQuiet(required any objectKey, - required any object, - any timeout="0", - any lastAccessTimeout="0", - any extra) output=false{ - + * Sets an object in the cache with no event calls and returns an instance of itself + * + * @objectKey The object cache key + * @object The object to cache + * @timeout The timeout to use on the object (if any, provider specific) + * @lastAccessTimeout The idle timeout to use on the object (if any, provider specific) + * @extra A map of name-value pairs to use as extra arguments to pass to a providers set operation + * + * @return ICacheProvider + */ + function setQuiet( + required objectKey, + required object, + timeout=0, + lastAccessTimeout=0, + struct extra + ){ + // check if incoming timeout is a timespan or minute to convert to timespan, do also checks if empty strings if( findnocase("string", arguments.timeout.getClass().getName() ) ){ if( len(arguments.timeout) ){ arguments.timeout = createTimeSpan(0,0,arguments.timeout,0); } @@ -466,118 +409,139 @@ component serializable="false" implements="wirebox.system.cache.ICacheProvider"{ if( len(arguments.lastAccessTimeout) ){ arguments.lastAccessTimeout = createTimeSpan(0,0,arguments.lastAccessTimeout,0); } else{ arguments.lastAccessTimeout = 0; } } - + var thisCacheName = getConfiguration().cacheName; if ( thisCacheName == "object" ) { - // if we passed object to the cache put CF would use a user defined custom "object" cache rather than the default - cachePut(arguments.objectKey,arguments.object,arguments.timeout,arguments.lastAccessTimeout); - + cachePut( + arguments.objectKey, + arguments.object, + arguments.timeout, + arguments.lastAccessTimeout + ); } else { - - cachePut(arguments.objectKey,arguments.object,arguments.timeout,arguments.lastAccessTimeout, thisCacheName); - + cachePut( + arguments.objectKey, + arguments.object, + arguments.timeout, + arguments.lastAccessTimeout, + thisCacheName + ); } - - return true; - } - + + return this; + } + /** - * get cache size - */ - any function getSize() output=false{ + * Get the number of elements in the cache + */ + numeric function getSize(){ return getObjectStore().getSize(); } - + /** - * Not implemented, let ehCache due its thang! - */ - void function reap() output=false{ - // Not implemented, let ehCache due its thang! + * Send a reap or flush command to the cache: Not implemented by this provider + * + * @return ICacheProvider + */ + function reap(){ + return this; } - + /** - * clear all elements from cache - */ - void function clearAll() output=false{ - var iData = { - cache = this - }; - - getObjectStore().removeAll(); - - // notify listeners - getEventManager().processState("afterCacheClearAll",iData); + * Clear all the cache elements from the cache + * + * @return ICacheProvider + */ + function clearAll(){ + var thisCacheName = getConfiguration().cacheName; + if ( thisCacheName == "object") { + cacheRemoveAll(); + } else { + cacheRemoveAll( thisCacheName ); + } + + // notify listeners + getEventManager().processState( "afterCacheClearAll", { cache = this } ); } - - /** - * clear an element from cache - */ - any function clear(required any objectKey) output=false{ + /** + * Clears an object from the cache by using its cache key. Returns false if object was not removed or did not exist anymore + * + * @objectKey The object cache key + */ + boolean function clear( required objectKey ){ var thisCacheName = getConfiguration().cacheName; - if ( thisCacheName == "object") { + if ( thisCacheName == "object") { cacheRemove( arguments.objectKey, false ); } else { cacheRemove( arguments.objectKey, false, thisCacheName ); } - + //ColdBox events - var iData = { + getEventManager().processState( "afterCacheElementRemoved", { cache = this, cacheObjectKey = arguments.objectKey - }; - getEventManager().processState("afterCacheElementRemoved",iData); - - return true; - } - - /** - * clear with no stats - */ - any function clearQuiet(required any objectKey) output=false{ - getObjectStore().removeQuiet( ucase(arguments.objectKey) ); + } ); + return true; } - + /** - * Clear by key snippet - */ - void function clearByKeySnippet(required keySnippet,regex=false,async=false) output=false{ - var threadName = "clearByKeySnippet_#replace(instance.uuidHelper.randomUUID(),"-","","all")#"; - - // Async? IF so, do checks - if( arguments.async AND NOT instance.utility.inThread() ){ - thread name="#threadName#"{ - instance.elementCleaner.clearByKeySnippet(arguments.keySnippet,arguments.regex); - } - } - else{ - instance.elementCleaner.clearByKeySnippet(arguments.keySnippet,arguments.regex); + * Clears an object from the cache by using its cache key. Returns false if object was not removed or did not exist anymore without doing statistics or updating listeners + * + * @objectKey The object cache key + */ + boolean function clearQuiet( required objectKey ){ + if( server.coldfusion.productVersion.listFirst() == 2018 ){ + return getObjectStore().removeQuiet( arguments.objectKey ); + } else { + return getObjectStore().removeQuiet( ucase( arguments.objectKey ) ); } } - + /** - * not implemented by cache - */ - void function expireAll() output=false{ + * Expire all the elments in the cache (if supported by the provider): Not implemented by this cache + * + * @return ICacheProvider + */ + function expireAll(){ // Just try to evict stuff, not a way to expire all elements. getObjectStore().evictExpiredElements(); + return this; } - + /** - * not implemented by cache - */ - void function expireObject(required any objectKey) output=false{ - //not implemented + * Expires an object from the cache by using its cache key. Returns false if object was not removed or did not exist anymore (if supported by the provider) Not implemented by this cache + * + * @objectKey The object cache key + * + * @return ICacheProvider + */ + function expireObject( required objectKey ){ + // not implemented + return this; } - + + /******************************** PRIVATE ********************************/ + /** - * set the associated cache factory - */ - void function setCacheFactory(required any cacheFactory) output=false{ - instance.cacheFactory = arguments.cacheFactory; + * Checks if the default cache is in use or another cache region + */ + private boolean function isDefaultCache(){ + return ( getConfiguration().cacheName EQ variables.DEFAULTS.cacheName ); + } + + /** + * Validate the incoming configuration and make necessary defaults + * + * @return LuceeProvider + **/ + private function validateConfiguration(){ + // Add in settings not discovered + structAppend( variables.configuration, variables.DEFAULTS, false ); + return this; } } - + diff --git a/src/cfml/system/wirebox/system/cache/providers/CacheBoxColdBoxProvider.cfc b/src/cfml/system/wirebox/system/cache/providers/CacheBoxColdBoxProvider.cfc index 40923b1f1..12630b9c6 100644 --- a/src/cfml/system/wirebox/system/cache/providers/CacheBoxColdBoxProvider.cfc +++ b/src/cfml/system/wirebox/system/cache/providers/CacheBoxColdBoxProvider.cfc @@ -1,124 +1,171 @@ - - - - - - // superSize Me - super.init(); - - // Prefixes - this.VIEW_CACHEKEY_PREFIX = "cbox_view-"; - this.EVENT_CACHEKEY_PREFIX = "cbox_event-"; - - // URL Facade Utility - instance.eventURLFacade = CreateObject("component","wirebox.system.cache.util.EventURLFacade").init(this); - - // ColdBox linkage - instance.coldbox = ""; - - return this; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * @author Luis Majano + * + * This CacheBox provider communicates with the built in CacheBox caches + */ +component + accessors="true" + serializable="false" + implements="wirebox.system.cache.providers.IColdBoxProvider" + extends="wirebox.system.cache.providers.CacheBoxProvider" +{ + + // Cache Prefixes + this.VIEW_CACHEKEY_PREFIX = "cbox_view-"; + this.EVENT_CACHEKEY_PREFIX = "cbox_event-"; + + /** + * Constructor + */ + function init(){ + super.init(); + + // URL Facade Utility + variables.eventURLFacade = new wirebox.system.cache.util.EventURLFacade( this ); + + return this; + } + + /** + * Get the cached view key prefix which is necessary for view caching + */ + function getViewCacheKeyPrefix(){ + return this.VIEW_CACHEKEY_PREFIX; + }; + + /** + * Get the event cache key prefix which is necessary for event caching + */ + function getEventCacheKeyPrefix(){ + return this.EVENT_CACHEKEY_PREFIX; + } + + /** + * Get the coldbox application reference as wirebox.system.web.Controller + * + * @return wirebox.system.web.Controller + */ + function getColdbox(){ + return variables.coldbox; + } + + /** + * Set the ColdBox linkage into the provider + * + * @coldbox The ColdBox controller + * @coldbox.doc_generic wirebox.system.web.Controller + * + * @return IColdboxApplicationCache + */ + function setColdBox( required coldbox ){ + variables.coldbox = arguments.coldbox; + return this; + } + + /** + * Get the event caching URL facade utility that determines event caching + * + * @return wirebox.system.cache.util.EventURLFacade + */ + function getEventURLFacade(){ + return variables.eventURLFacade; + } + + /** + * Clears all events from the cache. + * + * @async If implemented, determines async or sync clearing. + * + * @return IColdboxApplicationCache + */ + function clearAllEvents( boolean async=false ){ + var threadName = "clearAllEvents_#replace(variables.uuidHelper.randomUUID(),"-","","all")#"; + + // Async? IF so, do checks + if( arguments.async AND NOT variables.utility.inThread() ){ + thread name="#threadName#"{ + variables.elementCleaner.clearAllEvents(); + } + } + else{ + variables.elementCleaner.clearAllEvents(); + } + return this; + } + + /** + * Clears all the event permutations from the cache according to snippet and querystring. Be careful when using incomplete event name with query strings as partial event names are not guaranteed to match with query string permutations + * + * @eventSnippet The event snippet to clear on. Can be partial or full + * @queryString If passed in, it will create a unique hash out of it. For purging purposes + * + * @return IColdboxApplicationCache + */ + function clearEvent( required eventSnippet, queryString="" ){ + variables.elementCleaner.clearEvent( arguments.eventsnippet, arguments.queryString ); + return this; + } + + /** + * Clears all views from the cache. + * + * @async Run command asynchronously or not + * + * @return IColdboxApplicationCache + */ + function clearAllViews( boolean async=false ){ + var threadName = "clearAllViews_#replace(variables.uuidHelper.randomUUID(),"-","","all")#"; + + // Async? IF so, do checks + if( arguments.async AND NOT variables.utility.inThread() ){ + thread name="#threadName#"{ + variables.elementCleaner.clearAllViews(); + } + } + else{ + variables.elementCleaner.clearAllViews(); + } + return this; + } + + /** + * Clears all the event permutations from the cache according to the list of snippets and querystrings. Be careful when using incomplete event name with query strings as partial event names are not guaranteed to match with query string permutations + * + * @eventSnippet The comma-delimmitted list event snippet to clear on. Can be partial or full + * @queryString The comma-delimmitted list of queryStrings passed in. If passed in, it will create a unique hash out of it. For purging purposes. If passed in the list length must be equal to the list length of the event snippets passed in + * + * @return IColdboxApplicationCache + */ + function clearEventMulti( required eventsnippets, queryString="" ){ + variables.elementCleaner.clearEventMulti(arguments.eventsnippets,arguments.queryString); + return this; + } + + /** + * Clears all view name permutations from the cache according to the view name + * + * @viewSnippet The view name snippet to purge from the cache + * + * @return IColdboxApplicationCache + */ + function clearView( required viewSnippet ){ + variables.elementCleaner.clearView(arguments.viewSnippet); + return this; + } + + /** + * Clears all view name permutations from the cache according to the view name. + * + * @viewSnippets The comma-delimmitted list or array of view snippet to clear on. Can be partial or full + * + * @return IColdboxApplicationCache + */ + function clearViewMulti( required viewSnippets ){ + variables.elementCleaner.clearView(arguments.viewsnippets); + return this; + } + +} \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/providers/CacheBoxProvider.cfc b/src/cfml/system/wirebox/system/cache/providers/CacheBoxProvider.cfc index 0eb3d08c8..89c280d11 100644 --- a/src/cfml/system/wirebox/system/cache/providers/CacheBoxProvider.cfc +++ b/src/cfml/system/wirebox/system/cache/providers/CacheBoxProvider.cfc @@ -1,627 +1,568 @@ - - - - - - - - // super size me - super.init(); - - // Logger object - instance.logger = ""; - // Runtime Java object - instance.javaRuntime = createObject("java", "java.lang.Runtime"); - // Locking Timeout - instance.lockTimeout = "15"; - // Eviction Policy - instance.evictionPolicy = ""; - // Element Cleaner Helper - instance.elementCleaner = CreateObject("component","wirebox.system.cache.util.ElementCleaner").init(this); - // Utilities - instance.utility = createObject("component","wirebox.system.core.util.Util"); - // UUID Helper - instance.uuidHelper = createobject("java", "java.util.UUID"); - - // CacheBox Provider Property Defaults - instance.DEFAULTS = { - objectDefaultTimeout = 60, - objectDefaultLastAccessTimeout = 30, - useLastAccessTimeouts = true, - reapFrequency = 2, - freeMemoryPercentageThreshold = 0, - evictionPolicy = "LRU", - evictCount = 1, - maxObjects = 200, - objectStore = "ConcurrentStore", - coldboxEnabled = false - }; - - return this; - - - - - - - var cacheConfig = getConfiguration(); - var key = ""; - - // Validate configuration values, if they don't exist, then default them to DEFAULTS - for(key in instance.DEFAULTS){ - if( NOT structKeyExists(cacheConfig, key) OR NOT len(cacheConfig[key]) ){ - cacheConfig[key] = instance.DEFAULTS[key]; - } - } - - - - - - - - - - - - - +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * @author Luis Majano + * + * This CacheBox provider is our own enterprise cache implementation with many options and storage providers. + * + * Properties + * - name : The cache name + * - enabled : Boolean flag if cache is enabled + * - reportingEnabled: Boolean falg if cache can report + * - stats : The statistics object + * - configuration : The configuration structure + * - cacheFactory : The linkage to the cachebox factory + * - eventManager : The linkage to the event manager + * - cacheID : The unique identity code of this CFC + */ +component + accessors="true" + serializable="false" + implements="wirebox.system.cache.providers.ICacheProvider" + extends="wirebox.system.cache.AbstractCacheBoxProvider" +{ + + /** + * The global element cleaner utility object + */ + property name="elementCleaner"; + + /** + * The default lock timeout for reap operations: Defaults to 15 seconds + */ + property name="lockTimeout" type="numeric"; + + /** + * The eviction policy to use on the cache storage: Defaults to LRU + * @doc_generic wirebox.system.cache.policies.IEvictionPolicy + */ + property name="evictionPolicy"; + + /** + * The object storage object + * @doc_generic wirebox.system.cache.store.IObjectStore + */ + property name="objectStore"; + + /** + * The cache stats object + * @doc_generic wirebox.system.cache.util.CacheStats + */ + property name="stats"; + + // CacheBox Provider Property Defaults + variables.DEFAULTS = { + objectDefaultTimeout = 60, + objectDefaultLastAccessTimeout = 30, + useLastAccessTimeouts = true, + reapFrequency = 2, + freeMemoryPercentageThreshold = 0, + evictionPolicy = "LRU", + evictCount = 1, + maxObjects = 200, + objectStore = "ConcurrentStore", + coldboxEnabled = false, + resetTimeoutOnAccess = false + }; + + /** + * Constructor + */ + function init(){ + // super size me + super.init(); + // Element Cleaner Helper + variables.elementCleaner = new wirebox.system.cache.util.ElementCleaner( this ); + // Runtime Java object + variables.javaRuntime = createObject( "java", "java.lang.Runtime" ); + // Logger object + variables.logger = ""; + // Locking Timeout + variables.lockTimeout = "15"; + // Eviction Policy + variables.evictionPolicy = ""; + // Stats + variables.stats = ""; + // Object Store + variables.objectStore = ""; + + return this; + } + + /** + * configure the cache for operation + * + * @return CacheBoxProvider + */ + function configure(){ + var cacheConfig = getConfiguration(); + + lock name="CacheBoxProvider.configure.#variables.cacheId#" type="exclusive" timeout="30" throwontimeout="true"{ // Prepare the logger - instance.logger = getCacheFactory().getLogBox().getLogger( this ); + variables.logger = getCacheFactory().getLogBox().getLogger( this ); - if( instance.logger.canDebug() ){ - instance.logger.debug("Starting up CacheBox Cache: #getName()# with configuration: #cacheConfig.toString()#"); + if( variables.logger.canDebug() ){ + variables.logger.debug( "Starting up CacheBox Cache: #getName()# with configuration: #cacheConfig.toString()#" ); } // Validate the configuration validateConfiguration(); // Prepare Statistics - instance.stats = CreateObject("component","wirebox.system.cache.util.CacheStats").init(this); + variables.stats = new wirebox.system.cache.util.CacheStats( this ); // Setup the eviction Policy to use - evictionPolicy = locateEvictionPolicy( cacheConfig.evictionPolicy ); - instance.evictionPolicy = CreateObject("component", evictionPolicy).init(this); + variables.evictionPolicy = createObject( "component", locateEvictionPolicy( cacheConfig.evictionPolicy ) ).init( this ); // Create the object store the configuration mandated - objectStore = locateObjectStore( cacheConfig.objectStore ); - instance.objectStore = CreateObject("component", objectStore).init(this); + variables.objectStore = createObject("component", locateObjectStore( cacheConfig.objectStore ) ).init( this ); // Enable cache - instance.enabled = true; + variables.enabled = true; // Enable reporting - instance.reportingEnabled = true; + variables.reportingEnabled = true; // startup message - if( instance.logger.canDebug() ){ - instance.logger.debug( "CacheBox Cache: #getName()# has been initialized successfully for operation" ); - } - - - - - - - - - if( instance.logger.canDebug() ) - instance.logger.debug("CacheBox Cache: #getName()# has been shutdown."); - - - - - - - - if( fileExists( expandPath("/wirebox/system/cache/policies/#arguments.policy#.cfc") ) ){ - return "wirebox.system.cache.policies.#arguments.policy#"; - } - return arguments.policy; - - - - - - - - if( fileExists( expandPath("/wirebox/system/cache/store/#arguments.store#.cfc") ) ){ - return "wirebox.system.cache.store.#arguments.store#"; - } - return arguments.store; - - - - - - - - - - - var returnStruct = structnew(); - var x = 1; - var thisKey = ""; - - // Normalize keys - if( isArray(arguments.keys) ){ - arguments.keys = arrayToList( arguments.keys ); - } - - // Loop on Keys - for(x=1;x lte listLen(arguments.keys);x++){ - thisKey = arguments.prefix & listGetAt(arguments.keys,x); - returnStruct[thiskey] = lookup( thisKey ); - } - - return returnStruct; - - - - - - - - if( lookupQuiet(arguments.objectKey) ){ - // record a hit - getStats().hit(); - return true; - } - - // record a miss - getStats().miss(); - - return false; - - - - - - - - // cleanup the key - arguments.objectKey = lcase(arguments.objectKey); - - return instance.objectStore.lookup( arguments.objectKey ); - - - - - - - - var refLocal = {}; - // cleanup the key - arguments.objectKey = lcase( arguments.objectKey ); - - // get quietly - refLocal.results = instance.objectStore.get( arguments.objectKey ); - if( !isNull( refLocal.results ) ){ - getStats().hit(); - return refLocal.results; - } - getStats().miss(); - // don't return anything = null - - - - - - - - - var refLocal = {}; - - // cleanup the key - arguments.objectKey = lcase( arguments.objectKey ); - - // get object from store - refLocal.results = instance.objectStore.getQuiet( arguments.objectKey ); - if( structKeyExists(refLocal, "results") ){ - return refLocal.results; - } - - // don't return anything = null - - - - - - - - - - - var returnStruct = structnew(); - var x = 1; - var thisKey = ""; - - // Normalize keys - if( isArray(arguments.keys) ){ - arguments.keys = arrayToList( arguments.keys ); - } - - // Clear Prefix - arguments.prefix = trim(arguments.prefix); - - // Loop keys - for(x=1;x lte listLen(arguments.keys);x=x+1){ - thisKey = arguments.prefix & listGetAt(arguments.keys,x); - if( lookup(thisKey) ){ - returnStruct[thiskey] = get(thisKey); - } - } - - return returnStruct; - - - - - - - - - - // Cleanup the key - arguments.objectKey = lcase(trim(arguments.objectKey)); - - // Check if in the pool first - if( instance.objectStore.getIndexer().objectExists(arguments.objectKey) ){ - return instance.objectStore.getIndexer().getObjectMetadata(arguments.objectKey); - } - - return structnew(); - - - - - - - - - - - var returnStruct = structnew(); - var x = 1; - var thisKey = ""; - - // Normalize keys - if( isArray(arguments.keys) ){ - arguments.keys = arrayToList( arguments.keys ); - } - - // Clear Prefix - arguments.prefix = trim(arguments.prefix); - - // Loop on Keys - for(x=1;x lte listLen(arguments.keys);x=x+1){ - thisKey = arguments.prefix & listGetAt(arguments.keys,x); - returnStruct[thiskey] = getCachedObjectMetadata(thisKey); - } - - return returnStruct; - - - - - - - - - - - - - var key = 0; - // Clear Prefix - arguments.prefix = trim(arguments.prefix); - // Loop Over mappings - for(key in arguments.mapping){ - // Cache theses puppies - set(objectKey=arguments.prefix & key,object=arguments.mapping[key],timeout=arguments.timeout,lastAccessTimeout=arguments.lastAccessTimeout); - } - - - - - - - - - - - - - - var refLocal = { - object = get( arguments.objectKey ) - }; - // Verify if it exists? if so, return it. - if( structKeyExists( refLocal, "object" ) ){ return refLocal.object; } - // else, produce it - - - - // double lock - refLocal.object = get( arguments.objectKey ); - if( not structKeyExists( refLocal, "object" ) ){ - // produce it - refLocal.object = arguments.produce(); - // store it - set( objectKey=arguments.objectKey, - object=refLocal.object, - timeout=arguments.timeout, - lastAccessTimeout=arguments.lastAccessTimeout, - extra=arguments.extra ); - } - - - - - - - - - - - - - - - - - var iData = ""; - // Check if updating or not - var refLocal = { - oldObject = getQuiet( arguments.objectKey ) - }; - - // save object - setQuiet(arguments.objectKey,arguments.object,arguments.timeout,arguments.lastAccessTimeout); - - // Announce update if it exists? - if( structKeyExists(refLocal,"oldObject") ){ - // interception Data - iData = { - cache = this, - cacheObjectKey = arguments.objectKey, - cacheNewObject = arguments.object, - cacheOldObject = refLocal.oldObject - }; - - // announce it - getEventManager().processState("afterCacheElementUpdated", iData); - } - - // interception Data - iData = { - cache = this, - cacheObject = arguments.object, - cacheObjectKey = arguments.objectKey, - cacheObjectTimeout = arguments.timeout, - cacheObjectLastAccessTimeout = arguments.lastAccessTimeout - }; - - // announce it - getEventManager().processState("afterCacheElementInsert", iData); - - return true; - - - - - - - - - - - - - - var isJVMSafe = true; - var config = getConfiguration(); - var iData = {}; - - // cleanup the key - arguments.objectKey = lcase(arguments.objectKey); - - // JVM Checks - if( config.freeMemoryPercentageThreshold NEQ 0 AND thresholdChecks(config.freeMemoryPercentageThreshold) EQ false){ - // evict some stuff - instance.evictionPolicy.execute(); - } - - // Max objects check - if( config.maxObjects NEQ 0 AND getSize() GTE config.maxObjects ){ - // evict some stuff - instance.evictionPolicy.execute(); - } - - // Provider Default Timeout checks - if( NOT len(arguments.timeout) OR NOT isNumeric(arguments.timeout) ){ - arguments.timeout = config.objectDefaultTimeout; - } - if( NOT len(arguments.lastAccessTimeout) OR NOT isNumeric(arguments.lastAccessTimeout) ){ - arguments.lastAccessTimeout = config.objectDefaultLastAccessTimeout; + if( variables.logger.canDebug() ){ + variables.logger.debug( "CacheBox Cache: #getName()# has been initialized successfully for operation" ); } - - // save object - instance.objectStore.set(arguments.objectKey,arguments.object,arguments.timeout,arguments.lastAccessTimeout); - + } + + return this; + } + + /** + * If the cache provider implements it, this returns the cache's object store. + * + * @return wirebox.system.cache.store.IObjectStore + */ + function getObjectStore(){ + return variables.objectStore; + } + + /** + * Shutdown command issued when CacheBox is going through shutdown phase + * + * @return LuceeProvider + */ + function shutdown(){ + //nothing to shutdown + if( variables.logger.canDebug() ){ + variables.logger.debug( "CacheBox Cache: #getName()# has been shutdown." ); + } + return this; + } + + /** + * Check if an object is in cache, if not found it records a miss. + * + * @objectKey The key to retrieve + */ + boolean function lookup( required objectKey ){ + if( lookupQuiet( arguments.objectKey ) ){ + // record a hit + getStats().hit(); return true; - - - - - - - - - - - var returnStruct = {}; - var x = 1; - var thisKey = ""; - - // Clear Prefix - arguments.prefix = trim(arguments.prefix); - - // array? - if( isArray(arguments.keys) ){ - arguments.keys = arrayToList( arguments.keys ); - } - - // Loop on Keys - for(x=1;x lte listLen(arguments.keys); x++){ - thisKey = arguments.prefix & listGetAt(arguments.keys,x); - returnStruct[thiskey] = clear(thisKey); - } - - return returnStruct; - - - - - - - - - - - - - - - - - - - - - - - - - - // clean key - arguments.objectKey = lcase(trim(arguments.objectKey)); - - // clear key - return instance.objectStore.clear( arguments.objectKey ); - - - - - - - - var clearCheck = clearQuiet( arguments.objectKey ); - var iData = { + } + + // record a miss + getStats().miss(); + + return false; + } + + /** + * Check if an object is in cache, no stats updated or listeners + * + * @objectKey The key to retrieve + */ + boolean function lookupQuiet( required objectKey ){ + // cleanup the key + arguments.objectKey = lcase( arguments.objectKey ); + + return variables.objectStore.lookup( arguments.objectKey ); + } + + /** + * Get an object from the cache + * + * @objectKey The key to retrieve + */ + function get( required objectKey ){ + // cleanup the key + arguments.objectKey = lcase( arguments.objectKey ); + + // get quietly + var results = variables.objectStore.get( arguments.objectKey ); + if( !isNull( local.results ) ){ + getStats().hit(); + return results; + } + getStats().miss(); + // don't return anything = null + } + + /** + * get an item silently from cache, no stats advised: Stats not available on lucee + * + * @objectKey The key to retrieve + */ + function getQuiet( required objectKey ){ + // cleanup the key + arguments.objectKey = lcase( arguments.objectKey ); + + // get object from store + var results = variables.objectStore.getQuiet( arguments.objectKey ); + if( !isNull( local.results ) ){ + return results; + } + // don't return anything = null + } + + /** + * Get a cache objects metadata about its performance. This value is a structure of name-value pairs of metadata. + * + * @objectKey The key to retrieve + */ + struct function getCachedObjectMetadata( required objectKey ){ + // Cleanup the key + arguments.objectKey = lcase( arguments.objectKey ); + + // Check if in the pool first + if( variables.objectStore.getIndexer().objectExists( arguments.objectKey ) ){ + return variables.objectStore.getIndexer().getObjectMetadata( arguments.objectKey ); + } + + return {}; + } + + /** + * Sets an object in the cache and returns an instance of itself + * + * @objectKey The object cache key + * @object The object to cache + * @timeout The timeout to use on the object (if any, provider specific) + * @lastAccessTimeout The idle timeout to use on the object (if any, provider specific) + * @extra A map of name-value pairs to use as extra arguments to pass to a providers set operation + * + * @return ICacheProvider + */ + function set( + required objectKey, + required object, + timeout="", + lastAccessTimeout="", + struct extra={} + ){ + // Check if updating or not + var oldObject = getQuiet( arguments.objectKey ); + + // save object + setQuiet( + arguments.objectKey, + arguments.object, + arguments.timeout, + arguments.lastAccessTimeout, + arguments.extra + ); + + // Announce update if it exists? + if( !isNull( local.oldObject ) ){ + // announce it + getEventManager().processState( "afterCacheElementUpdated", { + cache = this, + cacheObjectKey = arguments.objectKey, + cacheNewObject = arguments.object, + cacheOldObject = oldObject + } ); + } + + // announce it + getEventManager().processState( "afterCacheElementInsert", { + cache = this, + cacheObject = arguments.object, + cacheObjectKey = arguments.objectKey, + cacheObjectTimeout = arguments.timeout, + cacheObjectLastAccessTimeout = arguments.lastAccessTimeout + } ); + + return this; + } + + /** + * Sets an object in the cache with no event calls and returns an instance of itself + * + * @objectKey The object cache key + * @object The object to cache + * @timeout The timeout to use on the object (if any, provider specific) + * @lastAccessTimeout The idle timeout to use on the object (if any, provider specific) + * @extra A map of name-value pairs to use as extra arguments to pass to a providers set operation + * + * @return ICacheProvider + */ + function setQuiet( + required objectKey, + required object, + timeout="", + lastAccessTimeout="", + struct extra={} + ){ + + var isJVMSafe = true; + var config = getConfiguration(); + var iData = {}; + + // cleanup the key + arguments.objectKey = lcase( arguments.objectKey ); + + // JVM Checks + if( config.freeMemoryPercentageThreshold NEQ 0 + AND + thresholdChecks( config.freeMemoryPercentageThreshold ) EQ false + ){ + // evict some stuff + variables.evictionPolicy.execute(); + } + + // Max objects check + if( config.maxObjects NEQ 0 AND getSize() GTE config.maxObjects ){ + // evict some stuff + variables.evictionPolicy.execute(); + } + + // Provider Default Timeout checks + if( NOT len( arguments.timeout ) OR NOT isNumeric( arguments.timeout ) ){ + arguments.timeout = config.objectDefaultTimeout; + } + if( NOT len( arguments.lastAccessTimeout ) OR NOT isNumeric( arguments.lastAccessTimeout ) ){ + arguments.lastAccessTimeout = config.objectDefaultLastAccessTimeout; + } + + // save object + variables.objectStore.set( + arguments.objectKey, + arguments.object, + arguments.timeout, + arguments.lastAccessTimeout, + arguments.extra + ); + + return this; + } + + /** + * Clears an object from the cache by using its cache key. Returns false if object was not removed or did not exist anymore without doing statistics or updating listeners + * + * @objectKey The object cache key + */ + boolean function clearQuiet( required objectKey ){ + // clean key + arguments.objectKey = lcase( arguments.objectKey ); + + // clear key + return variables.objectStore.clear( arguments.objectKey ); + } + + /** + * Clears an object from the cache by using its cache key. Returns false if object was not removed or did not exist anymore + * + * @objectKey The object cache key + */ + boolean function clear( required objectKey ){ + var clearCheck = clearQuiet( arguments.objectKey ); + + // If cleared notify listeners + if( clearCheck ){ + getEventManager().processState( "afterCacheElementRemoved", { cache = this, cacheObjectKey = arguments.objectKey - }; - - // If cleared notify listeners - if( clearCheck ){ - getEventManager().processState("afterCacheElementRemoved",iData); + } ); + } + + return clearCheck; + } + + /** + * Clear all the cache elements from the cache + * + * @return ICacheProvider + */ + function clearAll(){ + variables.objectStore.clearAll(); + + // notify listeners + getEventManager().processState( "afterCacheClearAll", { cache = this } ); + + return this; + } + + /** + * Get the number of elements in the cache + */ + numeric function getSize(){ + return variables.objectStore.getSize(); + } + + /** + * Send a reap or flush command to the cache: Not implemented by this provider + * + * @return ICacheProvider + */ + function reap(){ + var threadName = "CacheBoxProvider.reap_#replace( randomUUID(), "-", "", "all" )#"; + + // Reap only if in frequency + if( dateDiff( "n", getStats().getLastReapDatetime(), now() ) GTE getConfiguration().reapFrequency ){ + if( !inThread() ){ + thread name="#threadName#"{ + variables._reap(); + } + } else { + variables._reap(); } - - return clearCheck; - - - - - - - var iData = { - cache = this - }; - - instance.objectStore.clearAll(); - - // notify listeners - getEventManager().processState("afterCacheClearAll",iData); - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - var keyIndex = 1; - var cacheKeys = ""; - var cacheKeysLen = 0; - var thisKey = ""; - var thisMD = ""; - var config = getConfiguration(); - var sTime = getTickCount(); - - - - - - + } + + return this; + } + + /** + * Expire all the elments in the cache (if supported by the provider): Not implemented by this cache + * + * @return ICacheProvider + */ + function expireAll(){ + return expireByKeySnippet( keySnippet=".*",regex=true ); + } + + /** + * Expires an object from the cache by using its cache key. Returns false if object was not removed or did not exist anymore (if supported by the provider) Not implemented by this cache + * + * @objectKey The object cache key + * + * @return ICacheProvider + */ + function expireObject( required objectKey ){ + variables.objectStore.expireObject( lcase( arguments.objectKey ) ); + return this; + } + + /** + * Expires an object from the cache by using its cache key. Returns false if object was not removed or did not exist anymore (if supported by the provider) Not implemented by this cache + * + * @objectKey The object cache key + * + * @return ICacheProvider + */ + function expireByKeySnippet( required keySnippet, boolean regex=false, boolean async=false ){ + arrayFilter( getKeys(), function( item ){ + // Using Regex? + if( regex ){ + return reFindnocase( keySnippet, item ); + } else { + return findNoCase( keySnippet, item ); + } + } ) + .each( function( item ){ + if( + variables.objectStore.lookup( item ) + AND + getCachedObjectMetadata( item ).timeout GT 0 + ){ + expireObject( item ); + } + } ); + + return this; + } + + /** + * Has the object key expired in the cache: NOT IMPLEMENTED IN THIS CACHE + * + * @objectKey The key to retrieve + */ + boolean function isExpired( required objectKey ){ + return variables.objectStore.isExpired( lcase( arguments.objectKey ) ); + } + + /** + * Get a structure of all the keys in the cache with their appropriate metadata structures. This is used to build the reporting.[keyX->[metadataStructure]] + */ + struct function getStoreMetadataReport(){ + return variables.objectStore.getIndexer().getPoolMetadata(); + } + + /** + * Get a key lookup structure where cachebox can build the report on. Ex: [timeout=timeout,lastAccessTimeout=idleTimeout]. It is a way for the visualizer to construct the columns correctly on the reports + */ + struct function getStoreMetadataKeyMap(){ + return { + timeout = "timeout", + hits = "hits", + lastAccessTimeout = "lastAccessTimeout", + created = "created", + lastAccessed = "LastAccessed", + isExpired = "isExpired" + }; + } + + /** + * Returns a list of all elements in the cache, whether or not they are expired + */ + array function getKeys(){ + return variables.objectStore.getKeys(); + } + + /*************************************** NON-INTERFACE METHODS ****************************************************/ + + /** + * Locate the eviction policy on disk + * + * @policy The policy on disk + * + * @return wirebox.system.cache.policies.IEvictionPolicy + */ + function locateEvictionPolicy( required policy ){ + if( fileExists( expandPath( "/wirebox/system/cache/policies/#arguments.policy#.cfc" ) ) ){ + return "wirebox.system.cache.policies.#arguments.policy#"; + } + return arguments.policy; + } + + /** + * Locate the object storage + * + * @store The store to use + * + * @return wirebox.system.cache.store.IObjectStore + */ + function locateObjectStore( required store ){ + if( fileExists( expandPath( "/wirebox/system/cache/store/#arguments.store#.cfc" ) ) ){ + return "wirebox.system.cache.store.#arguments.store#"; + } + return arguments.store; + } + + /** + * Reap the cache, clear out everything that is dead. + */ + private function _reap(){ + var keyIndex = 1; + var cacheKeys = ""; + var cacheKeysLen = 0; + var thisKey = ""; + var thisMD = ""; + var config = getConfiguration(); + var sTime = getTickCount(); + + lock type="exclusive" name="CacheBoxProvider.reap.#variables.cacheId#" timeout="#variables.lockTimeout#"{ // log it - if( instance.logger.canDebug() ) - instance.logger.debug( "Starting to reap CacheBoxProvider: #getName()#, id: #instance.cacheID#" ); + if( variables.logger.canDebug() ){ + variables.logger.debug( "Starting to reap CacheBoxProvider: #getName()#, id: #variables.cacheId#" ); + } // Run Storage reaping first, before our local algorithm - instance.objectStore.reap(); + variables.objectStore.reap(); // Let's Get our reaping vars ready, get a duplicate of the pool metadata so we can work on a good copy cacheKeys = getKeys(); @@ -647,30 +588,30 @@ Properties // Clear the object from cache if( clear( thisKey ) ){ // Announce Expiration only if removed, else maybe another thread cleaned it - announceExpiration(thisKey); + announceExpiration( thisKey ); } continue; } - //Check for creation timeouts and clear - if ( dateDiff("n", thisMD.created, now() ) GTE thisMD.timeout ){ + // Check for creation timeouts and clear + if ( dateDiff( "n", thisMD.created, now() ) GTE thisMD.timeout ){ // Clear the object from cache if( clear( thisKey ) ){ // Announce Expiration only if removed, else maybe another thread cleaned it - announceExpiration(thisKey); + announceExpiration( thisKey ); } continue; } - //Check for last accessed timeouts. If object has not been accessed in the default span + // Check for last accessed timeouts. If object has not been accessed in the default span if ( config.useLastAccessTimeouts AND - dateDiff("n", thisMD.LastAccessed, now() ) gte thisMD.LastAccessTimeout ){ + dateDiff( "n", thisMD.lastAccessed, now() ) gte thisMD.lastAccessTimeout ){ // Clear the object from cache if( clear( thisKey ) ){ // Announce Expiration only if removed, else maybe another thread cleaned it - announceExpiration(thisKey); + announceExpiration( thisKey ); } continue; } @@ -682,135 +623,44 @@ Properties getStats().setLastReapDatetime( now() ); // log it - if( instance.logger.canDebug() ) - instance.logger.debug( "Finished reap in #getTickCount()-sTime#ms for CacheBoxProvider: #getName()#, id: #instance.cacheID#" ); - - - - - - - - expireByKeySnippet(keySnippet=".*",regex=true); - - - - - - - - instance.objectStore.expireObject( lcase(trim(arguments.objectKey)) ); - - - - - - - - - - - var keyIndex = 1; - var cacheKeys = getKeys(); - var cacheKeysLen = arrayLen(cacheKeys); - var tester = 0; - - // Loop Through Metadata - for (keyIndex=1; keyIndex LTE cacheKeysLen; keyIndex++){ - - // Using Regex? - if( arguments.regex ){ - tester = reFindnocase(arguments.keySnippet, cacheKeys[keyIndex]); - } - else{ - tester = findnocase(arguments.keySnippet, cacheKeys[keyIndex]); - } - - // Check if object still exists - if( tester - AND instance.objectStore.lookup( cacheKeys[keyIndex] ) - AND getCachedObjectMetadata(cacheKeys[keyIndex]).timeout GT 0){ - - expireObject( cacheKeys[keyIndex] ); - - } - }//end key loops - - - - - - - - - - - - - - - - - - var target = instance.objectStore.getIndexer().getPoolMetadata(); - - return target; - - - - - - - var keyMap = { - timeout = "timeout", hits = "hits", lastAccessTimeout = "lastAccessTimeout", - created = "created", LastAccessed = "LastAccessed", isExpired="isExpired" - }; - return keymap; - - - - - - - - - - - - - - - - - - - - var iData = { - cache = this, - cacheObjectKey = arguments.objectKey - }; - // Execute afterCacheElementExpired Interception - getEventManager().processState("afterCacheElementExpired",iData); - - - - - - - - var check = true; - var jvmThreshold = 0; - - try{ - jvmThreshold = ( (instance.javaRuntime.getRuntime().freeMemory() / instance.javaRuntime.getRuntime().maxMemory() ) * 100 ); - check = arguments.threshold LT jvmThreshold; - } - catch(any e){ - check = true; - } - - return check; - - - - \ No newline at end of file + if( variables.logger.canDebug() ) + variables.logger.debug( "Finished reap in #getTickCount()-sTime#ms for CacheBoxProvider: #getName()#, id: #variables.cacheId#" ); + } + } + + /******************************** PRIVATE ********************************/ + + /** + * Announce a key expiration + * + * @objectKey The key target + * + * @result CacheBoxProvider + */ + private function announceExpiration( required objectKey ){ + // Execute afterCacheElementExpired Interception + getEventManager().processState( "afterCacheElementExpired", { + cache = this, + cacheObjectKey = arguments.objectKey + } ); + + return this; + } + + /** + * JVM Threshold checks + * + * @threshold The threshold to check + */ + private boolean function thresholdChecks( required threshold ){ + try{ + var jvmThreshold = ( ( variables.javaRuntime.getRuntime().freeMemory() / variables.javaRuntime.getRuntime().maxMemory() ) * 100 ); + var check = ( arguments.threshold LT jvmThreshold ); + } catch( any e ) { + var check = true; + } + + return check; + } + +} \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/providers/ICacheProvider.cfc b/src/cfml/system/wirebox/system/cache/providers/ICacheProvider.cfc new file mode 100644 index 000000000..e1cbf5f20 --- /dev/null +++ b/src/cfml/system/wirebox/system/cache/providers/ICacheProvider.cfc @@ -0,0 +1,258 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * @author Luis Majano + * + * The main interface for a CacheBox cache provider. You need to implement all the methods in order for CacheBox to work correctly for the implementing cache provider. + * Many of the methods return itself, so they are documented in the
@return
annotation since interfaces are very janky in acf11 and 2016 + */ +interface{ + + /** + * Get the name of this cache + */ + function getName(); + + /** + * Set the cache name + * + * @name The name to set + * + * @return ICacheProvider + */ + function setName( required name ); + + /** + * Returns a flag indicating if the cache is ready for operation + */ + boolean function isEnabled(); + + /** + * Returns a flag indicating if the cache has reporting enabled + */ + boolean function isReportingEnabled(); + + /** + * Get the cache statistics object as wirebox.system.cache.util.IStats + * + * @return wirebox.system.cache.util.IStats + */ + function getStats(); + + /** + * Clear the cache statistics + * + * @return ICacheProvider + */ + function clearStatistics(); + + /** + * Get the structure of configuration parameters for the cache + */ + struct function getConfiguration(); + + /** + * Set the entire configuration structure for this cache + * + * @configuration The cache configuration + * + * @return ICacheProvider + */ + function setConfiguration( required struct configuration ); + + /** + * Get the cache factory reference this cache provider belongs to + */ + wirebox.system.cache.CacheFactory function getCacheFactory(); + + /** + * Set the cache factory reference for this cache + * + * @cacheFactory The cache factory + * @cacheFactory.doc_generic wirebox.system.cache.CacheFactory + * + * @return ICacheProvider + */ + function setCacheFactory( required cacheFactory ); + + /** + * Get this cache managers event listener manager + */ + function getEventManager(); + + /** + * Set the event manager for this cache + * + * @eventManager The event manager to set + * + * @return ICacheProvider + */ + function setEventManager( required eventManager ); + + /** + * This method makes the cache ready to accept elements and run. Usualy a cache is first created (init), then wired and then the factory calls configure() on it + * + * @return ICacheProvider + */ + function configure(); + + /** + * Shutdown command issued when CacheBox is going through shutdown phase + * + * @return ICacheProvider + */ + function shutdown(); + + /** + * If the cache provider implements it, this returns the cache's object store. + * + * @return wirebox.system.cache.store.IObjectStore or any depending on the cache implementation + */ + function getObjectStore(); + + /** + * Get a structure of all the keys in the cache with their appropriate metadata structures. This is used to build the reporting.[keyX->[metadataStructure]] + */ + struct function getStoreMetadataReport(); + + /** + * Get a key lookup structure where cachebox can build the report on. Ex: [timeout=timeout,lastAccessTimeout=idleTimeout]. It is a way for the visualizer to construct the columns correctly on the reports + */ + struct function getStoreMetadataKeyMap(); + + /** + * Returns a list of all elements in the cache, whether or not they are expired + */ + array function getKeys(); + + /** + * Get a cache objects metadata about its performance. This value is a structure of name-value pairs of metadata. + * + * @objectKey The key to retrieve + */ + struct function getCachedObjectMetadata( required objectKey ); + + /** + * Get an object from the cache and updates stats + * + * @objectKey The key to retrieve + */ + function get( required objectKey ); + + /** + * Get an object from the cache without updating stats or listners + * + * @objectKey The key to retrieve + */ + function getQuiet( required objectKey ); + + /** + * Has the object key expired in the cache + * + * @objectKey The key to retrieve + */ + boolean function isExpired( required objectKey ); + + /** + * Check if an object is in cache, if not found it records a miss. + * + * @objectKey The key to retrieve + */ + boolean function lookup( required objectKey ); + + /** + * Check if an object is in cache, no stats updated or listeners + * + * @objectKey The key to retrieve + */ + boolean function lookupQuiet( required objectKey ); + + /** + * Sets an object in the cache and returns an instance of itself + * + * @objectKey The object cache key + * @object The object to cache + * @timeout The timeout to use on the object (if any, provider specific) + * @lastAccessTimeout The idle timeout to use on the object (if any, provider specific) + * @extra A map of name-value pairs to use as extra arguments to pass to a providers set operation + * + * @return ICacheProvider + */ + function set( + required objectKey, + required object, + timeout, + lastAccessTimeout, + struct extra + ); + + /** + * Sets an object in the cache with no event calls and returns an instance of itself + * + * @objectKey The object cache key + * @object The object to cache + * @timeout The timeout to use on the object (if any, provider specific) + * @lastAccessTimeout The idle timeout to use on the object (if any, provider specific) + * @extra A map of name-value pairs to use as extra arguments to pass to a providers set operation + * + * @return ICacheProvider + */ + function setQuiet( + required objectKey, + required object, + timeout, + lastAccessTimeout, + struct extra + ); + + /** + * Get the number of elements in the cache + */ + numeric function getSize(); + + /** + * Send a reap or flush command to the cache + * + * @return ICacheProvider + */ + function reap(); + + /** + * Clear all the cache elements from the cache + * + * @return ICacheProvider + */ + function clearAll(); + + /** + * Clears an object from the cache by using its cache key. Returns false if object was not removed or did not exist anymore + * + * @objectKey The object cache key + */ + boolean function clear( required objectKey ); + + /** + * Clears an object from the cache by using its cache key. Returns false if object was not removed or did not exist anymore without doing statistics or updating listeners + * + * @objectKey The object cache key + */ + boolean function clearQuiet( required objectKey ); + + /** + * Expire all the elments in the cache (if supported by the provider) + * + * @return ICacheProvider + */ + function expireAll(); + + /** + * Expires an object from the cache by using its cache key. Returns false if object was not removed or did not exist anymore (if supported by the provider) + * + * @objectKey The object cache key + * + * @return ICacheProvider + */ + function expireObject( required objectKey ); + +} \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/providers/IColdBoxProvider.cfc b/src/cfml/system/wirebox/system/cache/providers/IColdBoxProvider.cfc new file mode 100644 index 000000000..c6f654357 --- /dev/null +++ b/src/cfml/system/wirebox/system/cache/providers/IColdBoxProvider.cfc @@ -0,0 +1,101 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * @author Luis Majano + * + * The main interface to produce a ColdBox Application cache. + */ +interface extends="wirebox.system.cache.providers.ICacheProvider"{ + + /** + * Get the cached view key prefix which is necessary for view caching + */ + function getViewCacheKeyPrefix(); + + /** + * Get the event cache key prefix which is necessary for event caching + */ + function getEventCacheKeyPrefix(); + + /** + * Get the coldbox application reference as wirebox.system.web.Controller + * + * @return wirebox.system.web.Controller + */ + function getColdbox(); + + /** + * Set the ColdBox linkage into the provider + * + * @coldbox The ColdBox controller + * @coldbox.doc_generic wirebox.system.web.Controller + * + * @return IColdboxApplicationCache + */ + function setColdBox( required coldbox ); + + /** + * Get the event caching URL facade utility that determines event caching + * + * @return wirebox.system.cache.util.EventURLFacade + */ + function getEventURLFacade(); + + /** + * Clears all events from the cache. + * + * @async If implemented, determines async or sync clearing. + * + * @return IColdboxApplicationCache + */ + function clearAllEvents( boolean async ); + + /** + * Clears all the event permutations from the cache according to snippet and querystring. Be careful when using incomplete event name with query strings as partial event names are not guaranteed to match with query string permutations + * + * @eventSnippet The event snippet to clear on. Can be partial or full + * @queryString If passed in, it will create a unique hash out of it. For purging purposes + * + * @return IColdboxApplicationCache + */ + function clearEvent( required eventSnippet, queryString="" ); + + /** + * Clears all the event permutations from the cache according to the list of snippets and querystrings. Be careful when using incomplete event name with query strings as partial event names are not guaranteed to match with query string permutations + * + * @eventSnippet The comma-delimmitted list event snippet to clear on. Can be partial or full + * @queryString The comma-delimmitted list of queryStrings passed in. If passed in, it will create a unique hash out of it. For purging purposes. If passed in the list length must be equal to the list length of the event snippets passed in + * + * @return IColdboxApplicationCache + */ + function clearEventMulti( required eventsnippets, queryString="" ); + + /** + * Clears all view name permutations from the cache according to the view name + * + * @viewSnippet The view name snippet to purge from the cache + * + * @return IColdboxApplicationCache + */ + function clearView( required viewSnippet ); + + /** + * Clears all view name permutations from the cache according to the view name. + * + * @viewSnippets The comma-delimmitted list or array of view snippet to clear on. Can be partial or full + * + * @return IColdboxApplicationCache + */ + function clearViewMulti( required viewSnippets ); + + /** + * Clears all views from the cache. + * + * @async Run command asynchronously or not + * + * @return IColdboxApplicationCache + */ + function clearAllViews( boolean async ); + +} \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/providers/LuceeColdboxProvider.cfc b/src/cfml/system/wirebox/system/cache/providers/LuceeColdboxProvider.cfc index c018faa55..477137f31 100644 --- a/src/cfml/system/wirebox/system/cache/providers/LuceeColdboxProvider.cfc +++ b/src/cfml/system/wirebox/system/cache/providers/LuceeColdboxProvider.cfc @@ -1,105 +1,171 @@ /** -******************************************************************************** -Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp -www.ortussolutions.com -******************************************************************************** -Author: Luis Majano -Description: - -This CacheBox provider communicates with the built in caches in -the Lucee Engine for ColdBox applications. - -*/ -component serializable="false" extends="wirebox.system.cache.providers.LuceeProvider" implements="wirebox.system.cache.IColdboxApplicationCache"{ - - LuceeColdBoxProvider function init() output=false{ + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * @author Luis Majano + * + * This CacheBox provider communicates with the built in caches in the Lucee Engine for ColdBox Apps + */ +component + accessors="true" + serializable="false" + implements="wirebox.system.cache.providers.IColdBoxProvider" + extends="wirebox.system.cache.providers.LuceeProvider" +{ + + // Cache Prefixes + this.VIEW_CACHEKEY_PREFIX = "lucee_view-"; + this.EVENT_CACHEKEY_PREFIX = "lucee_event-"; + + /** + * Constructor + */ + LuceeColdBoxProvider function init(){ super.init(); - - // Cache Prefixes - this.VIEW_CACHEKEY_PREFIX = "lucee_view-"; - this.EVENT_CACHEKEY_PREFIX = "lucee_event-"; - + // URL Facade Utility - instance.eventURLFacade = CreateObject("component","wirebox.system.cache.util.EventURLFacade").init(this); - + variables.eventURLFacade = new wirebox.system.cache.util.EventURLFacade( this ); + return this; } - - // Cache Key prefixes - any function getViewCacheKeyPrefix() output=false{ return this.VIEW_CACHEKEY_PREFIX; } - any function getEventCacheKeyPrefix() output=false{ return this.EVENT_CACHEKEY_PREFIX; } - - // set the coldbox controller - void function setColdbox(required any coldbox) output=false{ + + /** + * Get the cached view key prefix which is necessary for view caching + */ + function getViewCacheKeyPrefix(){ + return this.VIEW_CACHEKEY_PREFIX; + }; + + /** + * Get the event cache key prefix which is necessary for event caching + */ + function getEventCacheKeyPrefix(){ + return this.EVENT_CACHEKEY_PREFIX; + } + + /** + * Get the coldbox application reference as wirebox.system.web.Controller + * + * @return wirebox.system.web.Controller + */ + function getColdbox(){ + return variables.coldbox; + } + + /** + * Set the ColdBox linkage into the provider + * + * @coldbox The ColdBox controller + * @coldbox.doc_generic wirebox.system.web.Controller + * + * @return IColdboxApplicationCache + */ + function setColdBox( required coldbox ){ variables.coldbox = arguments.coldbox; + return this; } - - // Get ColdBox - any function getColdbox() output=false{ return coldbox; } - - // Get Event URL Facade Tool - any function getEventURLFacade() output=false{ return instance.eventURLFacade; } - + + /** + * Get the event caching URL facade utility that determines event caching + * + * @return wirebox.system.cache.util.EventURLFacade + */ + function getEventURLFacade(){ + return variables.eventURLFacade; + } + /** - * Clear all events - */ - void function clearAllEvents(async=false) output=false{ - var threadName = "clearAllEvents_#replace(instance.uuidHelper.randomUUID(),"-","","all")#"; - + * Clears all events from the cache. + * + * @async If implemented, determines async or sync clearing. + * + * @return IColdboxApplicationCache + */ + function clearAllEvents( boolean async=false ){ + var threadName = "clearAllEvents_#replace(variables.uuidHelper.randomUUID(),"-","","all")#"; + // Async? IF so, do checks - if( arguments.async AND NOT instance.utility.inThread() ){ + if( arguments.async AND NOT variables.utility.inThread() ){ thread name="#threadName#"{ - instance.elementCleaner.clearAllEvents(); + variables.elementCleaner.clearAllEvents(); } } else{ - instance.elementCleaner.clearAllEvents(); - } + variables.elementCleaner.clearAllEvents(); + } + return this; + } + + /** + * Clears all the event permutations from the cache according to snippet and querystring. Be careful when using incomplete event name with query strings as partial event names are not guaranteed to match with query string permutations + * + * @eventSnippet The event snippet to clear on. Can be partial or full + * @queryString If passed in, it will create a unique hash out of it. For purging purposes + * + * @return IColdboxApplicationCache + */ + function clearEvent( required eventSnippet, queryString="" ){ + variables.elementCleaner.clearEvent( arguments.eventsnippet, arguments.queryString ); + return this; } - + /** - * Clear all views - */ - void function clearAllViews(async=false) output=false{ - var threadName = "clearAllViews_#replace(instance.uuidHelper.randomUUID(),"-","","all")#"; - + * Clears all views from the cache. + * + * @async Run command asynchronously or not + * + * @return IColdboxApplicationCache + */ + function clearAllViews( boolean async=false ){ + var threadName = "clearAllViews_#replace(variables.uuidHelper.randomUUID(),"-","","all")#"; + // Async? IF so, do checks - if( arguments.async AND NOT instance.utility.inThread() ){ + if( arguments.async AND NOT variables.utility.inThread() ){ thread name="#threadName#"{ - instance.elementCleaner.clearAllViews(); + variables.elementCleaner.clearAllViews(); } } else{ - instance.elementCleaner.clearAllViews(); + variables.elementCleaner.clearAllViews(); } + return this; } - - /** - * Clear event - */ - void function clearEvent(required eventsnippet, queryString="") output=false{ - instance.elementCleaner.clearEvent(arguments.eventsnippet,arguments.queryString); - } - + /** - * Clear multiple events - */ - void function clearEventMulti(required eventsnippets,queryString="") output=false{ - instance.elementCleaner.clearEventMulti(arguments.eventsnippets,arguments.queryString); + * Clears all the event permutations from the cache according to the list of snippets and querystrings. Be careful when using incomplete event name with query strings as partial event names are not guaranteed to match with query string permutations + * + * @eventSnippet The comma-delimmitted list event snippet to clear on. Can be partial or full + * @queryString The comma-delimmitted list of queryStrings passed in. If passed in, it will create a unique hash out of it. For purging purposes. If passed in the list length must be equal to the list length of the event snippets passed in + * + * @return IColdboxApplicationCache + */ + function clearEventMulti( required eventsnippets, queryString="" ){ + variables.elementCleaner.clearEventMulti(arguments.eventsnippets,arguments.queryString); + return this; } - + /** - * Clear view - */ - void function clearView(required viewSnippet) output=false{ - instance.elementCleaner.clearView(arguments.viewSnippet); + * Clears all view name permutations from the cache according to the view name + * + * @viewSnippet The view name snippet to purge from the cache + * + * @return IColdboxApplicationCache + */ + function clearView( required viewSnippet ){ + variables.elementCleaner.clearView(arguments.viewSnippet); + return this; } - + /** - * Clear multiple view - */ - void function clearViewMulti(required viewsnippets) output=false{ - instance.elementCleaner.clearView(arguments.viewsnippets); + * Clears all view name permutations from the cache according to the view name. + * + * @viewSnippets The comma-delimmitted list or array of view snippet to clear on. Can be partial or full + * + * @return IColdboxApplicationCache + */ + function clearViewMulti( required viewSnippets ){ + variables.elementCleaner.clearView(arguments.viewsnippets); + return this; } - + } \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/providers/LuceeProvider.cfc b/src/cfml/system/wirebox/system/cache/providers/LuceeProvider.cfc index e393cc311..8609cbdbd 100644 --- a/src/cfml/system/wirebox/system/cache/providers/LuceeProvider.cfc +++ b/src/cfml/system/wirebox/system/cache/providers/LuceeProvider.cfc @@ -1,296 +1,228 @@ /** -******************************************************************************** -Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp -www.ortussolutions.com -******************************************************************************** -Author: Luis Majano -Description: - -This CacheBox provider communicates with the built in caches in -the Lucee Engine - -*/ -component serializable="false" implements="wirebox.system.cache.ICacheProvider"{ + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * @author Luis Majano + * + * This CacheBox provider communicates with the built in caches in the Lucee Engine + */ +component + accessors="true" + serializable="false" + implements="wirebox.system.cache.providers.ICacheProvider" + extends="wirebox.system.cache.AbstractCacheBoxProvider" +{ /** - * Constructor - */ - LuceeProvider function init() output=false{ - // prepare instance data - instance = { - // provider name - name = "", - // provider enable flag - enabled = false, - // reporting enabled flag - reportingEnabled = false, - // configuration structure - configuration = {}, - // cacheFactory composition - cacheFactory = "", - // event manager composition - eventManager = "", - // storage composition, even if it does not exist, depends on cache - store = "", - // the cache identifier for this provider - cacheID = createObject('java','java.lang.System').identityHashCode(this), - // Element Cleaner Helper - elementCleaner = CreateObject("component","wirebox.system.cache.util.ElementCleaner").init(this), - // Utilities - utility = createObject("component","wirebox.system.core.util.Util"), - // our UUID creation helper - uuidHelper = createobject("java", "java.util.UUID") - }; - - // Provider Property Defaults - instance.DEFAULTS = { - cacheName = "object" - }; - - return this; - } - - /** - * get the cache name - */ - any function getName() output=false{ - return instance.name; - } - - /** - * set the cache name - */ - void function setName(required name) output=false{ - instance.name = arguments.name; - } - - /** - * set the event manager - */ - void function setEventManager(required any EventManager) output=false{ - instance.eventManager = arguments.eventManager; - } - - /** - * get the event manager - */ - any function getEventManager() output=false{ - return instance.eventManager; - } - - /** - * get the cache configuration structure - */ - any function getConfiguration() output=false{ - return instance.configuration; - } - - /** - * set the cache configuration structure - */ - void function setConfiguration(required any configuration) output=false{ - instance.configuration = arguments.configuration; - } - - /** - * get the associated cache factory - */ - any function getCacheFactory() output=false{ - return instance.cacheFactory; - } - + * The global element cleaner utility object + */ + property name="elementCleaner"; + + // Provider Property Defaults STATIC + variables.DEFAULTS = { + cacheName = "object" + }; + /** - * Validate the incoming configuration and make necessary defaults - **/ - private void function validateConfiguration(){ - var cacheConfig = getConfiguration(); - var key = ""; - - // Validate configuration values, if they don't exist, then default them to DEFAULTS - for(key in instance.DEFAULTS){ - if( NOT structKeyExists(cacheConfig, key) OR NOT len(cacheConfig[key]) ){ - cacheConfig[key] = instance.DEFAULTS[key]; - } - } + * Constructor + */ + function init(){ + super.init(); + + // Element Cleaner Helper + variables.elementCleaner = new wirebox.system.cache.util.ElementCleaner( this ); + + return this; } - + /** - * configure the cache for operation - */ - void function configure() output=false{ - var config = getConfiguration(); - var props = []; - - lock name="LuceeProvider.config.#instance.cacheID#" type="exclusive" throwontimeout="true" timeout="20"{ - + * configure the cache for operation + * + * @return LuceeProvider + */ + function configure(){ + lock name="LuceeProvider.config.#variables.cacheID#" type="exclusive" throwontimeout="true" timeout="30"{ + // Prepare the logger - instance.logger = getCacheFactory().getLogBox().getLogger( this ); - - if( instance.logger.canDebug() ) - instance.logger.debug( "Starting up LuceeProvider Cache: #getName()# with configuration: #config.toString()#" ); - + variables.logger = getCacheFactory().getLogBox().getLogger( this ); + + if( variables.logger.canDebug() ){ + variables.logger.debug( "Starting up LuceeProvider Cache: #getName()# with configuration: #variables.configuration.toString()#" ); + } + // Validate the configuration validateConfiguration(); - + // enabled cache - instance.enabled = true; - instance.reportingEnabled = true; - - if( instance.logger.canDebug() ) - instance.logger.debug( "Cache #getName()# started up successfully" ); + variables.enabled = true; + variables.reportingEnabled = true; + + if( variables.logger.canDebug() ){ + variables.logger.debug( "Cache #getName()# started up successfully" ); + } } - + + return this; } - + /** - * shutdown the cache - */ - void function shutdown() output=false{ + * Shutdown command issued when CacheBox is going through shutdown phase + * + * @return LuceeProvider + */ + function shutdown(){ //nothing to shutdown - if( instance.logger.canDebug() ) - instance.logger.debug( "LuceeProvider Cache: #getName()# has been shutdown." ); - } - - /* - * Indicates if cache is ready for operation - */ - any function isEnabled() output=false{ - return instance.enabled; - } - - /* - * Indicates if cache is ready for reporting - */ - any function isReportingEnabled() output=false{ - return instance.reportingEnabled; - } - - /* - * Get the cache statistics object as wirebox.system.cache.util.ICacheStats - * @doc_generic wirebox.system.cache.util.ICacheStats - */ - any function getStats() output=false{ - return createObject("component", "wirebox.system.cache.providers.lucee-lib.LuceeStats").init( this ); + if( variables.logger.canDebug() ){ + variables.logger.debug( "LuceeProvider Cache: #getName()# has been shutdown." ); + } + return this; } - + /** - * clear the cache stats: Not enabled in this provider - */ - void function clearStatistics() output=false{ + * Get the cache statistics object as wirebox.system.cache.util.IStats + * + * @return wirebox.system.cache.util.IStats + */ + function getStats(){ + return new "wirebox.system.cache.providers.lucee-lib.LuceeStats"( this ); + } + + /** + * Clear the cache statistics + * THIS FUNCTION IS NOT IMPLEMENTED IN THIS PROVIDER + * + * @return ICacheProvider + */ + function clearStatistics(){ // not yet posible with lucee } - + /** - * Returns the underlying cache engine: Not enabled in this provider - */ - any function getObjectStore() output=false{ + * If the cache provider implements it, this returns the cache's object store. + * + * @return wirebox.system.cache.store.IObjectStore or any depending on the cache implementation + */ + function getObjectStore(){ // not yet possible with lucee //return cacheGetSession( getConfiguration().cacheName ); } - + /** - * get the cache's metadata report - */ - any function getStoreMetadataReport() output=false{ - var md = {}; - var keys = getKeys(); - var item = ""; - - for(item in keys){ - md[item] = getCachedObjectMetadata(item); - } - - return md; + * Get a structure of all the keys in the cache with their appropriate metadata structures. This is used to build the reporting.[keyX->[metadataStructure]] + */ + struct function getStoreMetadataReport(){ + return getKeys() + .reduce( function( item, result ){ + result[ item ] = getCachedObjectMetadata( item ); + return result; + }, {} ); } - + /** - * Get a key lookup structure where cachebox can build the report on. Ex: [timeout=timeout,lastAccessTimeout=idleTimeout]. It is a way for the visualizer to construct the columns correctly on the reports - */ - any function getStoreMetadataKeyMap() output="false"{ - var keyMap = { - timeout = "timespan", hits = "hitcount", lastAccessTimeout = "idleTime", - created = "createdtime", LastAccessed = "lasthit" - }; - return keymap; + * Get a key lookup structure where cachebox can build the report on. Ex: [timeout=timeout,lastAccessTimeout=idleTimeout]. It is a way for the visualizer to construct the columns correctly on the reports + */ + struct function getStoreMetadataKeyMap(){ + return { + timeout = "timespan", + hits = "hitcount", + lastAccessTimeout = "idleTime", + created = "createdtime", + lastAccessed = "lasthit" + }; } - + /** - * get all the keys in this provider - */ - any function getKeys() output=false{ + * Returns a list of all elements in the cache, whether or not they are expired + */ + array function getKeys(){ try{ if( isDefaultCache() ){ return cacheGetAllIds(); } - + return cacheGetAllIds( "", getConfiguration().cacheName ); - } - catch(Any e){ - instance.logger.error( "Error retrieving all keys from cache: #e.message# #e.detail#", e.stacktrace ); + } catch( Any e ) { + variables.logger.error( "Error retrieving all keys from cache: #e.message# #e.detail#", e.stacktrace ); return [ "Error retrieving keys from cache: #e.message#" ]; } } - + /** - * get an object's cached metadata - */ - any function getCachedObjectMetadata(required any objectKey) output=false{ + * Get a cache objects metadata about its performance. This value is a structure of name-value pairs of metadata. + * + * @objectKey The key to retrieve + */ + struct function getCachedObjectMetadata( required objectKey ){ if( isDefaultCache() ){ return cacheGetMetadata( arguments.objectKey ); } - + return cacheGetMetadata( arguments.objectKey, getConfiguration().cacheName ); } - + /** - * get an item from cache - */ - any function get(required any objectKey) output=false{ - + * Get an object from the cache + * + * @objectKey The key to retrieve + */ + function get( required objectKey ){ if( isDefaultCache() ){ return cacheGet( arguments.objectKey ); } - else{ - return cacheGet( arguments.objectKey, false, getConfiguration().cacheName ); - } + return cacheGet( arguments.objectKey, false, getConfiguration().cacheName ); } - + /** - * get an item silently from cache, no stats advised: Stats not available on lucee - */ - any function getQuiet(required any objectKey) output=false{ - // not implemented by lucee yet - return get(arguments.objectKey); + * get an item silently from cache, no stats advised: Stats not available on lucee + * + * @objectKey The key to retrieve + */ + function getQuiet( required objectKey ){ + return get( arguments.objectKey ); } - + /** - * Not implemented by this cache - */ - any function isExpired(required any objectKey) output=false{ + * Has the object key expired in the cache: NOT IMPLEMENTED IN THIS CACHE + * + * @objectKey The key to retrieve + */ + boolean function isExpired( required objectKey ){ return false; } - + /** - * check if object in cache - */ - any function lookup(required any objectKey) output=false{ + * Check if an object is in cache, if not found it records a miss. + * + * @objectKey The key to retrieve + */ + boolean function lookup( required objectKey ){ if( isDefaultCache() ){ - return cachekeyexists(arguments.objectKey ); + return cachekeyexists( arguments.objectKey ); } - return cachekeyexists(arguments.objectKey, getConfiguration().cacheName ); + return cachekeyexists( arguments.objectKey, getConfiguration().cacheName ); } - + /** - * check if object in cache with no stats: Stats not available on lucee - */ - any function lookupQuiet(required any objectKey) output=false{ + * Check if an object is in cache, no stats updated or listeners + * + * @objectKey The key to retrieve + */ + boolean function lookupQuiet( required objectKey ){ // not possible yet on lucee - return lookup(arguments.objectKey); + return lookup( arguments.objectKey ); } - + /** - * Tries to get an object from the cache, if not found, it calls the 'produce' closure to produce the data and cache it - */ + * Tries to get an object from the cache, if not found, it calls the 'produce' closure to produce the data and cache it + * + * @objectKey The object cache key + * @produce The producer closure/lambda + * @timeout The timeout to use on the object (if any, provider specific) + * @lastAccessTimeout The idle timeout to use on the object (if any, provider specific) + * @extra A map of name-value pairs to use as extra arguments to pass to a providers set operation + * + * @return The cached or produced data/object + */ any function getOrSet( required any objectKey, required any produce, @@ -298,66 +230,61 @@ component serializable="false" implements="wirebox.system.cache.ICacheProvider"{ any lastAccessTimeout="0", any extra={} ){ - - var refLocal = { - object = get( arguments.objectKey ) - }; - - // Verify if it exists? if so, return it. - if( structKeyExists( refLocal, "object" ) ){ return refLocal.object; } - - // else, produce it - lock name="CacheBoxProvider.GetOrSet.#instance.cacheID#.#arguments.objectKey#" type="exclusive" timeout="10" throwonTimeout="true"{ - // double lock - refLocal.object = get( arguments.objectKey ); - if( not structKeyExists( refLocal, "object" ) ){ - // produce it - refLocal.object = arguments.produce(); - // store it - set( objectKey=arguments.objectKey, - object=refLocal.object, - timeout=arguments.timeout, - lastAccessTimeout=arguments.lastAccessTimeout, - extra=arguments.extra ); - } - } - - return refLocal.object; + return super.getOrSet( argumentCollection=arguments ); } - + /** - * set an object in cache - */ - any function set(required any objectKey, - required any object, - any timeout="0", - any lastAccessTimeout="0", - any extra) output=false{ - - setQuiet(argumentCollection=arguments); - + * Sets an object in the cache and returns an instance of itself + * + * @objectKey The object cache key + * @object The object to cache + * @timeout The timeout to use on the object (if any, provider specific) + * @lastAccessTimeout The idle timeout to use on the object (if any, provider specific) + * @extra A map of name-value pairs to use as extra arguments to pass to a providers set operation + * + * @return ICacheProvider + */ + function set( + required objectKey, + required object, + timeout=0, + lastAccessTimeout=0, + struct extra + ){ + setQuiet( argumentCollection=arguments ); + //ColdBox events - var iData = { - cache = this, - cacheObject = arguments.object, - cacheObjectKey = arguments.objectKey, - cacheObjectTimeout = arguments.timeout, + var iData = { + cache = this, + cacheObject = arguments.object, + cacheObjectKey = arguments.objectKey, + cacheObjectTimeout = arguments.timeout, cacheObjectLastAccessTimeout = arguments.lastAccessTimeout - }; - getEventManager().processState("afterCacheElementInsert",iData); - - return true; - } - + }; + getEventManager().processState( "afterCacheElementInsert", iData ); + + return this; + } + /** - * set an object in cache with no advising to events - */ - any function setQuiet(required any objectKey, - required any object, - any timeout="0", - any lastAccessTimeout="0", - any extra) output=false{ - + * Sets an object in the cache with no event calls and returns an instance of itself + * + * @objectKey The object cache key + * @object The object to cache + * @timeout The timeout to use on the object (if any, provider specific) + * @lastAccessTimeout The idle timeout to use on the object (if any, provider specific) + * @extra A map of name-value pairs to use as extra arguments to pass to a providers set operation + * + * @return ICacheProvider + */ + function setQuiet( + required objectKey, + required object, + timeout=0, + lastAccessTimeout=0, + struct extra + ){ + // check if incoming timoeut is a timespan or minute to convert to timespan if( !findnocase("timespan", arguments.timeout.getClass().getName() ) ){ if( !isNumeric( arguments.timeout ) ){ arguments.timeout = 0; } @@ -369,125 +296,134 @@ component serializable="false" implements="wirebox.system.cache.ICacheProvider"{ } // Cache it if( isDefaultCache() ){ - cachePut(arguments.objectKey,arguments.object,arguments.timeout,arguments.lastAccessTimeout); - } - else{ - cachePut(arguments.objectKey,arguments.object,arguments.timeout,arguments.lastAccessTimeout, getConfiguration().cacheName); + cachePut( + arguments.objectKey, + arguments.object, + arguments.timeout, + arguments.lastAccessTimeout + ); + } else { + cachePut( + arguments.objectKey, + arguments.object, + arguments.timeout, + arguments.lastAccessTimeout, + getConfiguration().cacheName + ); } - - return true; - } - + + return this; + } + /** - * get cache size - */ - any function getSize() output=false{ + * Get the number of elements in the cache + */ + numeric function getSize(){ if( isDefaultCache() ){ return cacheCount(); } return cacheCount( getConfiguration().cacheName ); } - + /** - * Not implemented by this cache - */ - void function reap() output=false{ - // Not implemented by this provider + * Send a reap or flush command to the cache: Not implemented by this provider + * + * @return ICacheProvider + */ + function reap(){ + return this; } - + /** - * clear all elements from cache - */ - void function clearAll() output=false{ - var iData = { - cache = this - }; - + * Clear all the cache elements from the cache + * + * @return ICacheProvider + */ + function clearAll(){ if( isDefaultCache() ){ cacheClear(); + } else{ + cacheClear( "", getConfiguration().cacheName ); } - else{ - cacheClear("",getConfiguration().cacheName); - } - - // notify listeners - getEventManager().processState("afterCacheClearAll",iData); + + // notify listeners + getEventManager().processState( "afterCacheClearAll", { cache = this } ); + + return this; } - + /** - * clear an element from cache - */ - any function clear(required any objectKey) output=false{ - + * Clears an object from the cache by using its cache key. Returns false if object was not removed or did not exist anymore + * + * @objectKey The object cache key + */ + boolean function clear( required objectKey ){ + if( isDefaultCache() ){ cacheRemove( arguments.objectKey ); - } - else{ + } else { cacheRemove( arguments.objectKey ,false, getConfiguration().cacheName ); } - + //ColdBox events - var iData = { + getEventManager().processState( "afterCacheElementRemoved", { cache = this, cacheObjectKey = arguments.objectKey - }; - getEventManager().processState("afterCacheElementRemoved",iData); - + } ); + return true; } - + /** - * clear with no advising to events - */ - any function clearQuiet(required any objectKey) output=false{ + * Clears an object from the cache by using its cache key. Returns false if object was not removed or did not exist anymore without doing statistics or updating listeners + * + * @objectKey The object cache key + */ + boolean function clearQuiet( required objectKey ){ // normal clear, not implemented by lucee - clear(arguments.objectKey); - return true; + return clear( arguments.objectKey ); } - - /** - * Clear by key snippet - */ - void function clearByKeySnippet(required keySnippet, regex=false, async=false) output=false{ - var threadName = "clearByKeySnippet_#replace(instance.uuidHelper.randomUUID(),"-","","all")#"; - - // Async? IF so, do checks - if( arguments.async AND NOT instance.utility.inThread() ){ - thread name="#threadName#" keySnippet="#arguments.keySnippet#" regex="#arguments.regex#"{ - instance.elementCleaner.clearByKeySnippet( attribues.keySnippet, attribues.regex ); - } - } - else{ - instance.elementCleaner.clearByKeySnippet( arguments.keySnippet, arguments.regex ); - } - } - + /** - * not implemented by cache - */ - void function expireAll() output=false{ - // Not implemented by this cache + * Expire all the elments in the cache (if supported by the provider) + * THIS FUNCTION IS NOT IMPLEMENTED IN THIS PROVIDER + * + * @return ICacheProvider + */ + function expireAll(){ + return this; } - + /** - * not implemented by cache - */ - void function expireObject(required any objectKey) output=false{ - //not implemented + * Expires an object from the cache by using its cache key. Returns false if object was not removed or did not exist anymore (if supported by the provider) + * THIS FUNCTION IS NOT IMPLEMENTED IN THIS PROVIDER + * + * @objectKey The object cache key + * + * @return ICacheProvider + */ + function expireObject( required objectKey ){ + return this; } - + + /******************************** PRIVATE ********************************/ + /** - * Checks if the default cache is in use - */ - private any function isDefaultCache(){ - return ( getConfiguration().cacheName EQ instance.DEFAULTS.cacheName ); + * Checks if the default cache is in use or another cache region + */ + private boolean function isDefaultCache(){ + return ( getConfiguration().cacheName EQ variables.DEFAULTS.cacheName ); } - + /** - * set the associated cache factory - */ - void function setCacheFactory(required any cacheFactory) output=false{ - instance.cacheFactory = arguments.cacheFactory; + * Validate the incoming configuration and make necessary defaults + * + * @return LuceeProvider + **/ + private function validateConfiguration(){ + // Add in settings not discovered + structAppend( variables.configuration, variables.DEFAULTS, false ); + return this; } } \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/providers/MockProvider.cfc b/src/cfml/system/wirebox/system/cache/providers/MockProvider.cfc index a6da8918c..69f8ca989 100644 --- a/src/cfml/system/wirebox/system/cache/providers/MockProvider.cfc +++ b/src/cfml/system/wirebox/system/cache/providers/MockProvider.cfc @@ -1,238 +1,392 @@ - - - - - - - super.init(); - - instance.cache = {}; - - return this; - - - - - - - instance.cache = {}; - instance.enabled = true; - instance.reportingEnabled = true; - - - - - - - - - - - - - - - - - - - - - var keyMap = { - timeout = "timeout", hits = "hits", lastAccessTimeout = "lastAccessTimeout", - created = "created", LastAccessed = "LastAccessed", isExpire="isExpired" - }; - return keymap; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * ---- + * @luis Majano + * + * A mock cache provider that keeps cache data in a simple map for testing and assertions + **/ + component + accessors=true + serializable=false + implements="wirebox.system.cache.providers.IColdBoxProvider" + extends="wirebox.system.cache.AbstractCacheBoxProvider" +{ + + /** + * The in memory mocking cache + */ + property name="cache" type="struct"; + + // CacheBox Provider Property Defaults + variables.DEFAULTS = { + objectDefaultTimeout = 60, + objectDefaultLastAccessTimeout = 30, + useLastAccessTimeouts = true, + reapFrequency = 2, + freeMemoryPercentageThreshold = 0, + evictionPolicy = "LRU", + evictCount = 1, + maxObjects = 200, + objectStore = "ConcurrentStore", + coldboxEnabled = false, + resetTimeoutOnAccess = false + }; + + /** + * Constructor + * + * @return MockProvider + */ + function init(){ + super.init(); + + variables.cache = {}; + return this; + } + + /** + * This method makes the cache ready to accept elements and run. Usualy a cache is first created (init), then wired and then the factory calls configure() on it + * + * @return MockProvider + */ + function configure(){ + variables.cache = {}; + variables.enabled = true; + variables.reportingEnabled = true; + + validateConfiguration(); + + return this; + } + + /** + * Shutdown command issued when CacheBox is going through shutdown phase + * + * @return MockProvider + */ + function shutdown(){ + return this; + } + + /** + * If the cache provider implements it, this returns the cache's object store as type: wirebox.system.cache.store.IObjectStore + * + * @return wirebox.system.cache.store.IObjectStore + */ + function getObjectStore(){ + return variables.cache; + } + + /** + * Get a structure of all the keys in the cache with their appropriate metadata structures. This is used to build the reporting.[keyX->[metadataStructure]] + */ + struct function getStoreMetadataReport(){ + return {}; + } + + /** + * Get a key lookup structure where cachebox can build the report on. Ex: [timeout=timeout,lastAccessTimeout=idleTimeout]. It is a way for the visualizer to construct the columns correctly on the reports + */ + struct function getStoreMetadataKeyMap(){ + return { + timeout = "timeout", + hits = "hits", + lastAccessTimeout = "lastAccessTimeout", + created = "created", + lastAccessed = "lastAccessed", + isExpire = "isExpired" + }; + } + + /** + * Returns a list of all elements in the cache, whether or not they are expired + */ + array function getKeys(){ + return variables.cache.keyList(); + } + + /** + * Get a cache objects metadata about its performance. This value is a structure of name-value pairs of metadata. + * + * @objectKey The key to retrieve + */ + struct function getCachedObjectMetadata( required objectKey ){ + return {}; + } + + /** + * Get an object from the cache and updates stats + * + * @objectKey The key to retrieve + */ + function get( required objectKey ){ + return variables.cache[ arguments.objectKey ]; + } + + /** + * Get an object from the cache without updating stats or listners + * + * @objectKey The key to retrieve + */ + function getQuiet( required objectKey ){ + return variables.cache[ arguments.objectKey ]; + } + + /** + * Has the object key expired in the cache + * + * @objectKey The key to retrieve + */ + boolean function isExpired( required objectKey ){ + return lookup( arguments.objectKey ); + } + + /** + * Check if an object is in cache, if not found it records a miss. + * + * @objectKey The key to retrieve + */ + boolean function lookup( required objectKey ){ + return structKeyExists( variables.cache, arguments.objectKey ); + } + + /** + * Check if an object is in cache, no stats updated or listeners + * + * @objectKey The key to retrieve + */ + boolean function lookupQuiet( required objectKey ){ + return structKeyExists( variables.cache, arguments.objectKey ); + } + + /** + * Check if an object is in cache, if not found it records a miss. + * + * @objectValue The value to retrieve + */ + boolean function lookupValue( required objectValue ){ + return variables.cache.containsValue( arguments.objectValue ); + } + + /** + * Sets an object in the cache and returns an instance of itself + * + * @objectKey The object cache key + * @object The object to cache + * @timeout The timeout to use on the object (if any, provider specific) + * @lastAccessTimeout The idle timeout to use on the object (if any, provider specific) + * @extra A map of name-value pairs to use as extra arguments to pass to a providers set operation + * + * @return MockProvider + */ + function set( + required objectKey, + required object, + timeout, + lastAccessTimeout, + struct extra + ){ + variables.cache[ arguments.objectKey ] = arguments.object; + return this; + } + + /** + * Sets an object in the cache with no event calls and returns an instance of itself + * + * @objectKey The object cache key + * @object The object to cache + * @timeout The timeout to use on the object (if any, provider specific) + * @lastAccessTimeout The idle timeout to use on the object (if any, provider specific) + * @extra A map of name-value pairs to use as extra arguments to pass to a providers set operation + * + * @return MockProvider + */ + function setQuiet( + required objectKey, + required object, + timeout, + lastAccessTimeout, + struct extra + ){ + variables.cache[ arguments.objectKey ] = arguments.object; + return this; + } + + /** + * Get the number of elements in the cache + */ + numeric function getSize(){ + return structCount( variables.cache ); + } + + /** + * Send a reap or flush command to the cache + * + * @return MockProvider + */ + function reap(){ + return this; + } + + /** + * Clear all the cache elements from the cache + * + * @return MockProvider + */ + function clearAll(){ + variables.cache = {}; + return this; + } + + /** + * Clears an object from the cache by using its cache key. Returns false if object was not removed or did not exist anymore + * + * @objectKey The object cache key + */ + boolean function clear( required objectKey ){ + return structDelete( variables.cache, arguments.objectKey, true ); + } + + /** + * Clears an object from the cache by using its cache key. Returns false if object was not removed or did not exist anymore without doing statistics or updating listeners + * + * @objectKey The object cache key + */ + boolean function clearQuiet( required objectKey ){ + return structDelete( variables.cache, arguments.objectKey, true ); + } + + /** + * Expire all the elments in the cache (if supported by the provider) + */ + function expireAll(){ + return this; + } + + /** + * Expires an object from the cache by using its cache key. Returns false if object was not removed or did not exist anymore (if supported by the provider) + * + * @objectKey The object cache key + * + * @return MockProvider + */ + function expireObject( required objectKey ){ + return this; + } + + + /*************************************** ColdBox Application Cache Methods ***************************************/ + + /** + * Get the cached view key prefix which is necessary for view caching + */ + function getViewCacheKeyPrefix(){ + return "mock"; + } + + /** + * Get the event cache key prefix which is necessary for event caching + */ + function getEventCacheKeyPrefix(){ + return "mock"; + } + + /** + * Get the coldbox application reference as wirebox.system.web.Controller + * + * @return wirebox.system.web.Controller + */ + function getColdbox(){ + return variables.coldbox; + } + + /** + * Set the ColdBox linkage into the provider + * + * @coldbox The ColdBox controller + * @coldbox.doc_generic wirebox.system.web.Controller + * + * @return MockProvider + */ + function setColdBox( required coldbox ){ + variables.coldbox = arguments.coldbox; + return this; + } + + /** + * Get the event caching URL facade utility that determines event caching + * + * @return wirebox.system.cache.util.EventURLFacade + */ + function getEventURLFacade(){ + return variables.eventURLFacade; + } + + /** + * Clears all events from the cache. + * + * @async If implemented, determines async or sync clearing. + * + * @return MockProvider + */ + function clearAllEvents( boolean async=false ){ + return this; + } + + /** + * Clears all the event permutations from the cache according to snippet and querystring. Be careful when using incomplete event name with query strings as partial event names are not guaranteed to match with query string permutations + * + * @eventSnippet The event snippet to clear on. Can be partial or full + * @queryString If passed in, it will create a unique hash out of it. For purging purposes + * + * @return MockProvider + */ + function clearEvent( required eventSnippet, queryString="" ){ + return this; + } + + /** + * Clears all the event permutations from the cache according to the list of snippets and querystrings. Be careful when using incomplete event name with query strings as partial event names are not guaranteed to match with query string permutations + * + * @eventSnippet The comma-delimmitted list event snippet to clear on. Can be partial or full + * @queryString The comma-delimmitted list of queryStrings passed in. If passed in, it will create a unique hash out of it. For purging purposes. If passed in the list length must be equal to the list length of the event snippets passed in + * + * @return MockProvider + */ + function clearEventMulti( required eventsnippets, queryString="" ){ + return this; + } + + /** + * Clears all view name permutations from the cache according to the view name + * + * @viewSnippet The view name snippet to purge from the cache + * + * @return MockProvider + */ + function clearView( required viewSnippet ){ + return this; + } + + /** + * Clears all view name permutations from the cache according to the view name. + * + * @viewSnippets The comma-delimmitted list or array of view snippet to clear on. Can be partial or full + */ + function clearViewMulti( required viewSnippets ){ + return this; + } + + /** + * Clears all views from the cache. + * + * @async Run command asynchronously or not + * + * @return MockProvider + */ + function clearAllViews( boolean async=false ){ + return this; + } + +} \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/providers/cf-lib/CFStats.cfc b/src/cfml/system/wirebox/system/cache/providers/cf-lib/CFStats.cfc index d607f38cc..d62c403a8 100644 --- a/src/cfml/system/wirebox/system/cache/providers/cf-lib/CFStats.cfc +++ b/src/cfml/system/wirebox/system/cache/providers/cf-lib/CFStats.cfc @@ -5,64 +5,120 @@ www.ortussolutions.com ******************************************************************************** Author: Luis Majano Description: - + A coldfusion statistics object that communicates with the CF ehCache stats */ -component implements="wirebox.system.cache.util.ICacheStats" accessors="true"{ - +component implements="wirebox.system.cache.util.IStats" accessors="true"{ + property name="cacheStats" serializable="false"; + /** + * Constructor + * + * @stats Cache session stats + */ CFStats function init( stats ) output=false{ setCacheStats( arguments.stats ); return this; } - - any function getCachePerformanceRatio() output=false{ + + /** + * Get the cache's performance ratio + */ + numeric function getCachePerformanceRatio(){ var hits = getHits(); var requests = hits + getMisses(); - + if ( requests eq 0){ return 0; } - + return (hits/requests) * 100; } - - any function getObjectCount() output=false{ - return getCacheStats().getObjectCount(); + + /** + * Get the associated cache's live object count + */ + numeric function getObjectCount(){ + if( server.coldfusion.productVersion.listFirst() == 11 ){ + return getCacheStats().getObjectCount(); + } else { + return getCacheStats().getSize(); + } } - - void function clearStatistics() output=false{ - getCacheStats().clearStatistics(); + + /** + * Clear the stats + * + * @return IStats + */ + function clearStatistics(){ + if( server.coldfusion.productVersion.listFirst() == 11 ){ + getCacheStats().clearStatistics(); + } + return this; } - - any function getGarbageCollections() output=false{ + + /** + * Get the total cache's garbage collections + */ + numeric function getGarbageCollections(){ return 0; } - - any function getEvictionCount() output=false{ - return getCacheStats().getEvictionCount(); + + /** + * Get the total cache's eviction count + */ + numeric function getEvictionCount(){ + if( server.coldfusion.productVersion.listFirst() == 11 ){ + return getCacheStats().getEvictionCount(); + } else { + return getCacheStats().cacheEvictionCount(); + } } - - any function getHits() output=false{ - return getCacheStats().getCacheHits(); + + /** + * Get the total cache's hits + */ + numeric function getHits(){ + if( server.coldfusion.productVersion.listFirst() == 11 ){ + return getCacheStats().getCacheHits(); + } else { + return getCacheStats().cacheHitCount(); + } } - - any function getMisses() output=false{ - return getCacheStats().getCacheMisses(); + + /** + * Get the total cache's misses + */ + numeric function getMisses(){ + if( server.coldfusion.productVersion.listFirst() == 11 ){ + return getCacheStats().getCacheMisses(); + } else { + return getCacheStats().cacheMissCount(); + } } - - any function getLastReapDatetime() output=false{ + + /** + * Get the date/time of the last reap the cache did + * + * @return date/time or empty + */ + function getLastReapDatetime(){ return ""; } - + /******************************************************* ehCache specific functions ********************************************************/ + any function getAverageGetTime(){ - return getCacheStats().getAverageGetTime(); + if( server.coldfusion.productVersion.listFirst() == 11 ){ + return getCacheStats().getAverageGetTime(); + } else { + return ""; + } } - -} - + +} \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/providers/lucee-lib/LuceeStats.cfc b/src/cfml/system/wirebox/system/cache/providers/lucee-lib/LuceeStats.cfc index 8aeb5088b..99aaf9737 100644 --- a/src/cfml/system/wirebox/system/cache/providers/lucee-lib/LuceeStats.cfc +++ b/src/cfml/system/wirebox/system/cache/providers/lucee-lib/LuceeStats.cfc @@ -5,64 +5,98 @@ www.ortussolutions.com ******************************************************************************** Author: Luis Majano Description: - + A coldfusion statistics object that communicates with the lucee cache stats */ -component implements="wirebox.system.cache.util.ICacheStats" accessors="true"{ - +component implements="wirebox.system.cache.util.IStats" accessors="true"{ + property name="cacheProvider" serializable="false"; - LuceeStats function init( cacheProvider ) output=false{ + /** + * Constructor + * + * @cacheProvider The associated cache manager/provider of type: wirebox.system.cache.providers.ICacheProvider + * @cacheProvider.doc_generic wirebox.system.cache.providers.ICacheProvider + */ + function init( required cacheProvider ){ setCacheProvider( arguments.cacheProvider ); return this; } - - any function getCachePerformanceRatio() output=false{ + + /** + * Get the cache's performance ratio + */ + numeric function getCachePerformanceRatio(){ var hits = getHits(); var requests = hits + getMisses(); - + if ( requests eq 0){ return 0; } - + return (hits/requests) * 100; } - - any function getObjectCount() output=false{ + + /** + * Get the associated cache's live object count + */ + numeric function getObjectCount(){ return cacheCount( getCacheProvider().getConfiguration().cacheName ); } - - void function clearStatistics() output=false{ - // not yet implemented by lucee + + /** + * Clear the stats + * + * @return IStats + */ + function clearStatistics(){ + return this; } - - any function getGarbageCollections() output=false{ + + /** + * Get the total cache's garbage collections + */ + numeric function getGarbageCollections(){ return 0; } - - any function getEvictionCount() output=false{ + + /** + * Get the total cache's eviction count + */ + numeric function getEvictionCount(){ return 0; } - - any function getHits() output=false{ + + /** + * Get the total cache's hits + */ + numeric function getHits() { var props = cacheGetProperties( getCacheProvider().getConfiguration().cacheName ); if( arrayLen( props ) and structKeyExists( props[ 1 ], "hit_count" ) ){ return props[ 1 ].hit_count; } return 0; } - - any function getMisses() output=false{ + + /** + * Get the total cache's misses + */ + numeric function getMisses(){ var props = cacheGetProperties( getCacheProvider().getConfiguration().cacheName ); if( arrayLen( props ) and structKeyExists( props[ 1 ], "miss_count" ) ){ return props[ 1 ].miss_count; } return 0; } - - any function getLastReapDatetime() output=false{ + + /** + * Get the date/time of the last reap the cache did + * + * @return date/time or empty + */ + function getLastReapDatetime(){ return ""; } -} - + +} \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/report/ReportHandler.cfc b/src/cfml/system/wirebox/system/cache/report/ReportHandler.cfc index c2c1dbbda..a42d8c81d 100644 --- a/src/cfml/system/wirebox/system/cache/report/ReportHandler.cfc +++ b/src/cfml/system/wirebox/system/cache/report/ReportHandler.cfc @@ -1,182 +1,184 @@ - - - - - - - - - - - - - - variables.cacheBox = arguments.cacheBox; - variables.baseURL = arguments.baseURL; - variables.runtime = createObject("java", "java.lang.Runtime"); - variables.skin = arguments.skin; - variables.skinPath = "/wirebox/system/cache/report/skins/#arguments.skin#"; - // Store tag attributes so they are available on skin templates. - variables.attributes = arguments.attributes; - // Caller references - variables.caller = arguments.caller; - - return this; - - - - - - - - - - - - // Commands - switch(arguments.command){ - // Cache Commands - case "expirecache" : { cacheBox.getCache(arguments.cacheName).expireAll(); break; } - case "clearcache" : { cacheBox.getCache(arguments.cacheName).clearAll(); break; } - case "reapcache" : { cacheBox.getCache(arguments.cacheName).reap(); break;} - case "delcacheentry" : { cacheBox.getCache(arguments.cacheName).clear( arguments.cacheEntry );break;} - case "expirecacheentry" : { cacheBox.getCache(arguments.cacheName).expireObject( arguments.cacheEntry );break;} - case "clearallevents" : { cacheBox.getCache(arguments.cacheName).clearAllEvents();break;} - case "clearallviews" : { cacheBox.getCache(arguments.cacheName).clearAllViews();break;} - case "cacheBoxReapAll" : { cacheBox.reapAll();break;} - case "cacheBoxExpireAll" : { cacheBox.expireAll();break;} - case "gc" : { runtime.getRuntime().gc(); break;} - - default: return false; +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * @author Luis Majano + * + * The ColdBox CacheBox Report Handler + */ +component accessors="true" serializable="false"{ + + /** + * Constructor + * + * @cacheBox The cache factory binded to + * @baseUrl The baseURL used for reporting + * @skin The skin to use for reporting + * @attributes The incoming attributes + * @caller Access to the caller tag + */ + function init( + required wirebox.system.cache.CacheFactory cacheBox, + required baseUrl, + required skin, + required struct attributes, + required caller + ){ + variables.cacheBox = arguments.cacheBox; + variables.baseURL = arguments.baseURL; + variables.runtime = createObject( "java", "java.lang.Runtime" ); + variables.skin = arguments.skin; + variables.skinPath = "/wirebox/system/cache/report/skins/#arguments.skin#"; + // Store tag attributes so they are available on skin templates. + variables.attributes = arguments.attributes; + // Caller references + variables.caller = arguments.caller; + + return this; + } + + /** + * Process CacheBox Commands + * + * @command The command to process + * @cacheName The cache name, defaults to `default` + * @cacheEntry The cache entry to act upon + */ + boolean function processCommands( command="", cacheName="default", cacheEntry="" ){ + // Commands + switch( arguments.command ){ + // Cache Commands + case "expirecache" : { cacheBox.getCache( arguments.cacheName ).expireAll(); break; } + case "clearcache" : { cacheBox.getCache( arguments.cacheName ).clearAll(); break; } + case "reapcache" : { cacheBox.getCache( arguments.cacheName ).reap(); break;} + case "delcacheentry" : { cacheBox.getCache( arguments.cacheName ).clear( arguments.cacheEntry ); break;} + case "expirecacheentry" : { cacheBox.getCache( arguments.cacheName ).expireObject( arguments.cacheEntry ); break;} + case "clearallevents" : { cacheBox.getCache( arguments.cacheName ).clearAllEvents(); break;} + case "clearallviews" : { cacheBox.getCache( arguments.cacheName ).clearAllViews(); break;} + case "cacheBoxReapAll" : { cacheBox.reapAll(); break;} + case "cacheBoxExpireAll" : { cacheBox.expireAll(); break;} + case "gc" : { runtime.getRuntime().gc(); break;} + + default: return false; + } + + return true; + } + + /** + * Renders the caching panel. + */ + function renderCachePanel(){ + var cacheNames = variables.cacheBox.getCacheNames(); + var UrlBase = variables.baseURL; + + savecontent variable="local.content"{ + include "#skinPath#/CachePanel.cfm"; + } + + return local.content; + } + + /** + * Render a cache report for a specific cache + * + * @cacheName The cache name + */ + function renderCacheReport( cacheName="default" ){ + // Cache info + var cacheProvider = variables.cacheBox.getCache( arguments.cacheName ); + var cacheConfig = ""; + var cacheStats = ""; + var cacheSize = cacheProvider.getSize(); + var isCacheBox = true; + + // JVM Data + var JVMRuntime = variables.runtime.getRuntime(); + var JVMFreeMemory = JVMRuntime.freeMemory()/1024; + var JVMTotalMemory = JVMRuntime.totalMemory()/1024; + var JVMMaxMemory = JVMRuntime.maxMemory()/1024; + + // URL Base + var URLBase = variables.baseURL; + + // Prepare cache report for cachebox + cacheConfig = cacheProvider.getConfiguration(); + cacheStats = cacheProvider.getStats(); + + savecontent variable="local.content"{ + include "#skinPath#/CacheReport.cfm"; + } + + return local.content; + } + + /** + * Render a cache's content report + * + * @cacheName The cache name + */ + function renderCacheContentReport( cacheName="default" ){ + var thisKey = ""; + var x = ""; + var cacheProvider = variables.cacheBox.getCache( arguments.cacheName ); + + // URL Base + var URLBase = variables.baseURL; + + // Cache Data + var cacheMetadata = cacheProvider.getStoreMetadataReport(); + var cacheMDKeyLookup = cacheProvider.getStoreMetadataKeyMap(); + var cacheKeys = cacheProvider.getKeys(); + var cacheKeysLen = arrayLen( cacheKeys ); + + // Sort Keys + arraySort( cacheKeys, "textnocase" ); + + savecontent variable="local.content"{ + include "#skinPath#/CacheContentReport.cfm"; + } + + return local.content; + } + + /** + * Renders the caching key value dumper. + * + * @cacheName The cache name + * @cacheEntry The cache entry to dump + */ + function renderCacheDumper( cacheName="default", required cacheEntry ){ + var cachekey = UrlDecode( arguments.cacheEntry ); + var dumperContents = "NOT_FOUND"; + var cache = variables.cacheBox.getCache( arguments.cacheName ); + + // check key + if ( !len( cacheKey ) || !cache.lookup( cacheKey ) ) { + return dumperContents; + } + + // Get Data + var cacheValue = cache.get( cacheKey ); + + // Dump it out + if ( isSimpleValue( cacheValue ) ) { + savecontent variable="dumperContents" { + writeOutput( "#cachekey# = #cacheValue#" ); + } + } else { + savecontent variable="dumperContents" { + writeDump( var=cacheValue, top=5, label=cachekey ); } - - return true; - - - - - - - var content = ""; - var cacheNames = cacheBox.getCacheNames(); - var URLBase = baseURL; - - - - - - - - - - - - - var content = ""; - - // Cache info - var cacheProvider = cacheBox.getCache( arguments.cacheName ); - var cacheConfig = ""; - var cacheStats = ""; - var cacheSize = cacheProvider.getSize(); - var isCacheBox = true; - - // JVM Data - var JVMRuntime = runtime.getRuntime(); - var JVMFreeMemory = JVMRuntime.freeMemory()/1024; - var JVMTotalMemory = JVMRuntime.totalMemory()/1024; - var JVMMaxMemory = JVMRuntime.maxMemory()/1024; - - // URL Base - var URLBase = baseURL; - - // Prepare cache report for cachebox - cacheConfig = cacheProvider.getConfiguration(); - cacheStats = cacheProvider.getStats(); - - - - - - - - - - - - - var thisKey = ""; - var x = ""; - var content = ""; - var cacheProvider = cacheBox.getCache( arguments.cacheName ); - var cacheKeys = ""; - var cacheKeysLen = 0; - var cacheMetadata = ""; - var cacheMDKeyLookup = structnew(); - - // URL Base - var URLBase = baseURL; - - // Cache Data - cacheMetadata = cacheProvider.getStoreMetadataReport(); - cacheMDKeyLookup = cacheProvider.getStoreMetadataKeyMap(); - cacheKeys = cacheProvider.getKeys(); - cacheKeysLen = arrayLen( cacheKeys ); - - // Sort Keys - arraySort( cacheKeys ,"textnocase" ); - - - - - - - - - - - - - - - - - - - - - - - - - - - - #cachekey# = #cacheValue# - - - - - - - - - - - - - - - - \ No newline at end of file + } + + return dumperContents; + } + + /** + * Get utility object + */ + private function getUtil(){ + return new wirebox.system.core.util.Util(); + } + +} \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/report/monitor.cfm b/src/cfml/system/wirebox/system/cache/report/monitor.cfm index 672bfe78d..516c08c87 100644 --- a/src/cfml/system/wirebox/system/cache/report/monitor.cfm +++ b/src/cfml/system/wirebox/system/cache/report/monitor.cfm @@ -11,7 +11,7 @@ Description : ATTRIBUTES: - cacheBox : An instance reference to the cacheBox factory to report on -- baseURL (optional='default') : An optional baseURL that will be used to post to this monitor on. Default is cgi.script_name +- baseURL (optional='default') : An optional baseURL that will be used to post to this monitor on. Default is CGI.SCRIPT_NAME - skin (optional='default') : The skin to render the report in, it uses 'default' skin by default. -----------------------------------------------------------------------> @@ -26,7 +26,7 @@ ATTRIBUTES: - + diff --git a/src/cfml/system/wirebox/system/cache/store/BlackholeStore.cfc b/src/cfml/system/wirebox/system/cache/store/BlackholeStore.cfc index 5da10fecf..ba0d4b3bf 100644 --- a/src/cfml/system/wirebox/system/cache/store/BlackholeStore.cfc +++ b/src/cfml/system/wirebox/system/cache/store/BlackholeStore.cfc @@ -1,119 +1,162 @@ - - - - - - - - - - // Store Fields - var fields = "hits,timeout,lastAccessTimeout,created,LastAccessed,isExpired,isSimple"; - var config = arguments.cacheProvider.getConfiguration(); - - // Prepare instance - instance = { - cacheProvider = arguments.cacheProvider, - storeID = 'blackhole' - }; - - return this; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * @author Luis Majano + * + * I am the fastest way to cache objects. I am so fast because I dont do anything. I'm really a tool to use when working on caching strategies. When I am in use nothing is cached. It just vanishes. + */ +component implements="wirebox.system.cache.store.IObjectStore" accessors=true{ + + /** + * The cache provider reference + */ + property name="cacheProvider" doc_generic="wirebox.system.cache.providers.ICacheProvider"; + + /** + * The human store name + */ + property name="storeID"; + + /** + * Constructor + * + * @cacheProvider The associated cache provider as wirebox.system.cache.providers.ICacheProvider + * @cacheprovider.doc_generic wirebox.system.cache.providers.ICacheProvider + */ + function init( required cacheProvider ){ + // Store Fields + var fields = "hits,timeout,lastAccessTimeout,created,LastAccessed,isExpired,isSimple"; + var config = arguments.cacheProvider.getConfiguration(); + + // Prepare instance + variables.cacheProvider = arguments.cacheProvider; + variables.storeID = 'blackhole'; + + return this; + } + + /** + * Flush the store to a permanent storage + */ + void function flush(){ + return; + } + + /** + * Reap the storage + */ + void function reap(){ + return; + } + + /** + * Clear all the elements in the store + */ + void function clearAll(){ + return; + } + + /** + * Get the store's pool metadata indexer structure + * + * @return wirebox.system.cache.store.indexers.MetadataIndexer + */ + function getIndexer(){ + return; + } + + /** + * Get all the store's object keys array + * + * @return array + */ + function getKeys(){ + return; + } + + /** + * Check if an object is in the store + * + * @objectKey The key to lookup + * + * @return boolean + */ + function lookup( required objectKey ){ + return false; + } + + /** + * Get an object from the store with metadata tracking + * + * @objectKey The key to retrieve + */ + function get( required objectKey ){ + return; + } + + /** + * Get an object from cache with no metadata tracking + * + * @objectKey The key to retrieve + */ + function getQuiet( required objectKey ){ + return; + } + + /** + * Expire an object + * + * @objectKey The key to expire + */ + void function expireObject( required objectKey ){ + return; + } + + /** + * Expire check + * + * @objectKey The key to check + * + * @return boolean + */ + function isExpired( required objectKey ){ + return; + } + + /** + * Sets an object in the storage + * + * @objectKey The object key + * @object The object to save + * @timeout Timeout in minutes + * @lastAccessTimeout Idle Timeout in minutes + * @extras A map of extra name-value pairs to store alongside the object + */ + void function set( + required objectKey, + required object, + timeout=0, + lastAccessTimeout=0, + extras={} + ){ + return; + } + + /** + * Clears an object from the storage + * + * @objectKey The object key to clear + */ + function clear( required objectKey ){ + return; + } + + /** + * Get the size of the store + */ + function getSize(){ + return 0; + } + +} \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/store/ConcurrentSoftReferenceStore.cfc b/src/cfml/system/wirebox/system/cache/store/ConcurrentSoftReferenceStore.cfc index 8a46799e6..69f232d95 100644 --- a/src/cfml/system/wirebox/system/cache/store/ConcurrentSoftReferenceStore.cfc +++ b/src/cfml/system/wirebox/system/cache/store/ConcurrentSoftReferenceStore.cfc @@ -1,251 +1,246 @@ - - - - - - - - - - // Super size me - super.init( arguments.cacheProvider ); - - // Override Fields - instance.indexer.setFields( instance.indexer.getFields() & ",isSoftReference"); - - // Prepare soft reference lookup maps - instance.softRefKeyMap = CreateObject("java","java.util.concurrent.ConcurrentHashMap").init(); - instance.referenceQueue = CreateObject("java","java.lang.ref.ReferenceQueue").init(); - - return this; - - - - - - - - - super.clearAll(); - instance.softRefKeyMap.clear(); - - - - - - - - - - - - // Init Ref Key Vars - refLocal.collected = instance.referenceQueue.poll(); - +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * @author Luis Majano + * + * I am a concurrent soft reference object store. In other words, I am fancy! + * This store is case-sensitive + */ +component extends="wirebox.system.cache.store.ConcurrentStore" accessors=true{ + + /** + * The reverse lookup map for soft references + */ + property name="softRefKeymap"; + + /** + * The Java soft reference queue used for reaps + */ + property name="referenceQueue"; + + /** + * Constructor + * + * @cacheProvider The associated cache provider as wirebox.system.cache.providers.ICacheProvider + * @cacheprovider.doc_generic wirebox.system.cache.providers.ICacheProvider + */ + function init( required cacheProvider ){ + // Super size me + super.init( arguments.cacheProvider ); + + // Override Fields + variables.indexer.setFields( variables.indexer.getFields() & ",isSoftReference" ); + + // Prepare soft reference lookup maps + variables.softRefKeyMap = createObject( "java", "java.util.concurrent.ConcurrentHashMap" ).init(); + variables.referenceQueue = createObject( "java", "java.lang.ref.ReferenceQueue" ).init(); + + return this; + } + + /** + * Clear all the elements in the store + */ + void function clearAll(){ + super.clearAll(); + variables.softRefKeyMap.clear(); + } + + /** + * Reap the storage, clean it from old stuff + */ + void function reap(){ + lock + name="ConcurrentSoftReferenceStore.reap.#variables.storeID#" + type="exclusive" + timeout="20"{ + + // Init Ref Key Vars + var collected = variables.referenceQueue.poll(); + // Let's reap the garbage collected soft references - while( structKeyExists(reflocal, "collected") ){ - + while( !isNull( local.collected ) ){ + // Clean if it still exists - if( softRefLookup( reflocal.collected ) ){ - + if( softRefLookup( collected ) ){ + // expire it - expireObject( getSoftRefKey(refLocal.collected) ); - + expireObject( getSoftRefKey( collected ) ); + // GC Collection Hit - instance.cacheProvider.getStats().gcHit(); + variables.cacheProvider.getStats().gcHit(); } - + // Poll Again - reflocal.collected = instance.referenceQueue.poll(); - } - - - - - - - - - - - - - - // check existence via super, if not found, check as it might be a soft reference - if( NOT super.lookup( arguments.objectKey ) ){ return false; } - // get quiet to test it as it might be a soft reference - refLocal.target = getQuiet( arguments.objectKey ); - // is it found? - if( NOT structKeyExists(refLocal,"target") ){ return false; } - - // if we get here, it is found - return true; - - - - - - - - - var refLocal = {}; - - // Get via concurrent store - refLocal.target = super.get( arguments.objectKey ); - if( !isNull( refLocal.target ) ){ - - // Validate if SR or normal object - if( isInstanceOf(refLocal.target, "java.lang.ref.SoftReference") ){ - return refLocal.target.get(); - } - - return refLocal.target; - } - - - - - - - - var refLocal = {}; - - // Get via concurrent store - refLocal.target = super.getQuiet( arguments.objectKey ); - - if( !isNull( refLocal.target ) ){ - - // Validate if SR or normal object - if( isInstanceOf(refLocal.target, "java.lang.ref.SoftReference") ){ - return refLocal.target.get(); - } - - return refLocal.target; - } - - - - - - - - - - - - - - - - - - - - - // Check for eternal object - if( isSR ){ - // Cache as soft reference not an eternal object - target = createSoftReference(arguments.objectKey,arguments.object); + collected = variables.referenceQueue.poll(); } - else{ - target = arguments.object; - } - - // Store it - super.set(objectKey=arguments.objectKey, - object=target, - timeout=arguments.timeout, - lastAccessTimeout=arguments.lastAccessTimeout, - extras=arguments.extras); - - // Set extra md in indexer - instance.indexer.setObjectMetadataProperty(arguments.objectKey,"isSoftReference", isSR ); - - - - - - - - - - - - - - // Check if it exists - if( NOT structKeyExists(instance.pool, arguments.objectKey) ){ - return false; - } - - // Is this a soft reference? - softRef = instance.pool[arguments.objectKey]; - - // Removal of Soft Ref Lookup - if( instance.indexer.getObjectMetadataProperty(arguments.objectKey,"isSoftReference") ){ - structDelete(getSoftRefKeyMap(),softRef); + } + } + + /** + * Check if an object is in cache + * + * @objectKey The key to lookup + * + * @return boolean + */ + function lookup( required objectKey ){ + // check existence via super, if not found, check as it might be a soft reference + if( NOT super.lookup( arguments.objectKey ) ){ + return false; + } + + // get quiet to test it as it might be a soft reference + if( isNull( getQuiet( arguments.objectKey ) ) ){ + return false; + } + + // if we get here, it is found + return true; + } + + /** + * Get an object from cache. If its a soft reference object it might return a `null` value. + * + * @objectKey The key to retrieve + */ + function get( required objectKey ){ + // Get via concurrent store + var target = super.get( arguments.objectKey ); + if( !isNull( local.target ) ){ + + // Validate if SR or normal object + if( isInstanceOf( target, "java.lang.ref.SoftReference" ) ){ + return target.get(); } - - return super.clear( arguments.objectKey ); - - - - - - - - - - - - - - - - - - - - - - - - - var keyMap = getSoftRefKeyMap(); - - if( structKeyExists(keyMap,arguments.softRef) ){ - return keyMap[arguments.softRef]; + + return target; + } + } + + /** + * Get an object from cache. If its a soft reference object it might return a null value + * + * @objectKey The key to retrieve + */ + function getQuiet( required objectKey ){ + // Get via concurrent store + var target = super.getQuiet( arguments.objectKey ); + if( !isNull( local.target ) ){ + + // Validate if SR or normal object + if( isInstanceOf( target, "java.lang.ref.SoftReference" ) ){ + return target.get(); } - - - - - - - - - - - - // Create Soft Reference Wrapper and register with Queue - var softRef = CreateObject("java","java.lang.ref.SoftReference").init(arguments.target,getReferenceQueue()); - var refKeyMap = getSoftRefKeyMap(); - - // Create Reverse Mapping - refKeyMap[ softRef ] = arguments.objectKey; - - return softRef; - - - - \ No newline at end of file + + return target; + } + } + + /** + * Sets an object in the storage + * + * @objectKey The object key" + * @object The object to save" + * @timeout Timeout in minutes" + * @lastAccessTimeout Idle Timeout in minutes" + * @extras A map of extra name-value pairs" + */ + void function set( + required objectKey, + required object, + timeout="", + lastAccessTimeout="", + extras={} + ){ + var target = 0; + var isSR = ( arguments.timeout GT 0 ); + + // Check for eternal object + if( isSR ){ + // Cache as soft reference not an eternal object + target = createSoftReference( arguments.objectKey, arguments.object ); + } else { + target = arguments.object; + } + + // Store it + super.set( + objectKey = arguments.objectKey, + object = target, + timeout = arguments.timeout, + lastAccessTimeout = arguments.lastAccessTimeout, + extras = arguments.extras + ); + + // Set extra md in indexer + variables.indexer.setObjectMetadataProperty( arguments.objectKey, "isSoftReference", isSR ); + } + + /** + * Clears an object from the storage pool + * + * @objectKey The object key to clear + */ + function clear( required objectKey ){ + // Check if it exists + if( NOT variables.pool.containsKey( arguments.objectKey ) ){ + return false; + } + + // Is this a soft reference? + var softRef = variables.pool.get( arguments.objectKey ); + + // Removal of Soft Ref Lookup + if( !isNull( local.softRef ) && variables.indexer.getObjectMetadataProperty( arguments.objectKey, "isSoftReference" ) ){ + variables.softRefKeyMap.remove( softRef.hashCode() ); + } + + return super.clear( arguments.objectKey ); + } + + /****************************************************************************************/ + /* EXTENSION METHODS */ + /****************************************************************************************/ + + /** + * See if the soft reference is in the reference key map + * + * @softRef The soft reference to verify + */ + boolean function softRefLookup( required softRef ){ + return variables.softRefKeyMap.containsKey( "hc-#arguments.softRef.hashCode()#" ); + } + + /** + * Get the soft reference's key from the soft reference lookback map + * + * @softRef The soft reference to check + * + * @return The object key it points to + */ + function getSoftRefKey( required softRef ){ + return variables.softRefKeyMap.get( "hc-#arguments.softRef.hashCode()#" ); + } + + /** + * Create SR, register cached object and reference + * + * @objectKey The value of the key to store + * @target The target to wrap + * + * @return A java soft reference `java.lang.ref.SoftReference` + */ + private function createSoftReference( required objectKey, required target ){ + // Create Soft Reference Wrapper and register with Queue + var softRef = createObject( "java", "java.lang.ref.SoftReference" ) + .init( arguments.target, variables.referenceQueue ); + + // Create Reverse Mapping, using CF approach or ACF blows up. + variables.softRefKeyMap.put( "hc-#softRef.hashCode()#", arguments.objectKey ); + + return softRef; + } + +} \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/store/ConcurrentStore.cfc b/src/cfml/system/wirebox/system/cache/store/ConcurrentStore.cfc index f9d54e6e0..565418366 100644 --- a/src/cfml/system/wirebox/system/cache/store/ConcurrentStore.cfc +++ b/src/cfml/system/wirebox/system/cache/store/ConcurrentStore.cfc @@ -1,213 +1,237 @@ - - - - - - - - - - // Indexing Fields - var fields = "hits,timeout,lastAccessTimeout,created,LastAccessed,isExpired"; - - // Prepare instance - instance = { - cacheProvider = arguments.cacheProvider, - storeID = createObject('java','java.lang.System').identityHashCode(this), - pool = createObject("java","java.util.concurrent.ConcurrentHashMap").init(), - indexer = createObject("component","wirebox.system.cache.store.indexers.MetadataIndexer").init(fields) - }; - - return this; - - - - - - - - - - - - - - - - - - - - - - instance.pool.clear(); - instance.indexer.clearAll(); - - - - - - - - - - - - - - - - - - - - - - - - - // Check if object in pool and object not dead - if( structKeyExists( instance.pool , arguments.objectKey) - AND instance.indexer.objectExists( arguments.objectKey ) - AND NOT instance.indexer.getObjectMetadataProperty(arguments.objectKey,"isExpired") ){ - return true; +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * @author Luis Majano + * + * I am a concurrent object store. In other words, I am fancy! This store is case-sensitive + */ +component implements="wirebox.system.cache.store.IObjectStore" accessors="true"{ + + /** + * The cache provider reference + */ + property name="cacheProvider" doc_generic="wirebox.system.cache.providers.ICacheProvider"; + + /** + * The human store name + */ + property name="storeID"; + + /** + * The storage pool + */ + property name="pool" doc_generic="java.util.concurrent.ConcurrentHashMap"; + + /** + * The metadata indexer object + */ + property name="indexer" doc_generic="wirebox.system.cache.store.indexers.MetadataIndexer"; + + /** + * Constructor + * + * @cacheProvider The associated cache provider as wirebox.system.cache.providers.ICacheProvider + * @cacheprovider.doc_generic wirebox.system.cache.providers.ICacheProvider + */ + function init( required cacheProvider ){ + // Indexing Fields + var fields = "hits,timeout,lastAccessTimeout,created,lastAccessed,isExpired"; + + // Prepare instance + variables.cacheProvider = arguments.cacheProvider; + variables.storeID = createObject( "java", "java.lang.System" ).identityHashCode( this ); + variables.pool = createObject( "java","java.util.concurrent.ConcurrentHashMap" ).init(); + variables.indexer = new wirebox.system.cache.store.indexers.MetadataIndexer( fields ); + variables.collections = createObject( "java", "java.util.Collections" ); + + return this; + } + + /** + * Flush the store to a permanent storage + */ + void function flush(){ + return; + } + + /** + * Reap the storage + */ + void function reap(){ + return; + } + + /** + * Get the store's pool metadata indexer structure + * + * @return wirebox.system.cache.store.indexers.MetadataIndexer + */ + function getIndexer(){ + return variables.indexer; + } + + /** + * Clear all the elements in the store + */ + void function clearAll(){ + variables.pool.clear(); + variables.indexer.clearAll(); + } + + /** + * Get all the store's object keys array + * + * @return array + */ + function getKeys(){ + return variables.collections.list( variables.pool.keys() ); + } + + /** + * Check if an object is in the store + * + * @objectKey The key to lookup + * + * @return boolean + */ + function lookup( required objectKey ){ + return ( + variables.pool.containsKey( arguments.objectKey ) AND + variables.indexer.objectExists( arguments.objectKey ) AND NOT + variables.indexer.getObjectMetadataProperty( arguments.objectKey, "isExpired" ) + ); + } + + /** + * Get an object from the store with metadata tracking, or null if not found + * + * @objectKey The key to retrieve + */ + function get( required objectKey ){ + // retrieve from map + var results = variables.pool.get( arguments.objectKey ); + if( !isNull( local.results ) ){ + + // Record Metadata Access + variables.indexer.setObjectMetadataProperty( + arguments.objectKey, + "hits", + variables.indexer.getObjectMetadataProperty( arguments.objectKey, "hits" ) + 1 + ); + variables.indexer.setObjectMetadataProperty( + arguments.objectKey, + "lastAccessed", + now() + ); + // Is resetTimeoutOnAccess enabled? If so, jump up the creation time to increase the timeout + if( variables.cacheProvider.getConfiguration().resetTimeoutOnAccess ){ + variables.indexer.setObjectMetadataProperty( + arguments.objectKey, + "created", + now() + ); } - + + // return object + return results; + } + } + + /** + * Get an object from cache with no metadata tracking + * + * @objectKey The key to retrieve + */ + function getQuiet( required objectKey ){ + // retrieve from map + var results = variables.pool.get( arguments.objectKey ); + if( !isNull( local.results ) ){ + return results; + } + } + + /** + * Expire an object + * + * @objectKey The key to expire + */ + void function expireObject( required objectKey ){ + variables.indexer.setObjectMetadataProperty( + arguments.objectKey, + "isExpired", + true + ); + } + + /** + * Expire check + * + * @objectKey The key to check + * + * @return boolean + */ + function isExpired( required objectKey ){ + return variables.indexer.getObjectMetadataProperty( arguments.objectKey, "isExpired" ); + } + + /** + * Sets an object in the storage + * + * @objectKey The object key + * @object The object to save + * @timeout Timeout in minutes + * @lastAccessTimeout Idle Timeout in minutes + * @extras A map of extra name-value pairs to store alongside the object + */ + void function set( + required objectKey, + required object, + timeout="", + lastAccessTimeout="", + extras={} + ){ + // Set new Object into cache pool + variables.pool.put( arguments.objectKey, arguments.object ); + + // Create object's metdata + var metaData = { + "hits" = 1, + "timeout" = arguments.timeout, + "lastAccessTimeout" = arguments.lastAccessTimeout, + "created" = now(), + "lastAccessed" = now(), + "isExpired" = false + }; + + // Save the object's metadata + variables.indexer.setObjectMetadata( arguments.objectKey, metaData ); + } + + /** + * Clears an object from the storage + * + * @objectKey The object key to clear + */ + function clear( required objectKey ){ + // Check if it exists + if( !variables.pool.containsKey( arguments.objectKey ) ) { return false; - - - - - - - - - - - - - // retrieve from map - refLocal.results = instance.pool.get( arguments.objectKey ); - if( !isNull( refLocal.results ) ){ - - // Record Metadata Access - instance.indexer.setObjectMetadataProperty(arguments.objectKey,"hits", instance.indexer.getObjectMetadataProperty(arguments.objectKey,"hits")+1); - instance.indexer.setObjectMetadataProperty(arguments.objectKey,"LastAccessed", now()); - - // return object - return refLocal.results; - } - - - - - - - - - - - - // retrieve from map - refLocal.results = instance.pool.get( arguments.objectKey ); - if( structKeyExists(refLocal,"results") ){ - return refLocal.results; - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Set new Object into cache pool - instance.pool[arguments.objectKey] = arguments.object; - - // Create object's metdata - metaData = { - hits = 1, - timeout = arguments.timeout, - lastAccessTimeout = arguments.LastAccessTimeout, - created = now(), - LastAccessed = now(), - isExpired = false - }; - - // Save the object's metadata - instance.indexer.setObjectMetadata(arguments.objectKey, metaData); - - - - - - - - - - - - - - // Check if it exists - if( NOT structKeyExists(instance.pool, arguments.objectKey) ){ - return false; - } - - // Remove it - structDelete(instance.pool, arguments.objectKey); - instance.indexer.clear( arguments.objectKey ); - - // Removed - return true; - - - - - - - - - - - - \ No newline at end of file + } + + // Remove it + variables.pool.remove( arguments.objectKey ); + variables.indexer.clear( arguments.objectKey ); + + // Removed + return true; + } + + /** + * Get the size of the store + */ + function getSize(){ + return variables.pool.size(); + } + +} \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/store/DiskStore.cfc b/src/cfml/system/wirebox/system/cache/store/DiskStore.cfc index 2aeb142b9..6e437dc14 100644 --- a/src/cfml/system/wirebox/system/cache/store/DiskStore.cfc +++ b/src/cfml/system/wirebox/system/cache/store/DiskStore.cfc @@ -1,263 +1,333 @@ - - - - - - - - - - // Store Fields - var fields = "hits,timeout,lastAccessTimeout,created,LastAccessed,isExpired,isSimple"; - var config = arguments.cacheProvider.getConfiguration(); - - // Prepare instance - instance = { - cacheProvider = arguments.cacheProvider, - storeID = createObject('java','java.lang.System').identityHashCode(this), - indexer = createObject("component","wirebox.system.cache.store.indexers.MetadataIndexer").init(fields), - converter = createObject("component","wirebox.system.core.conversion.ObjectMarshaller").init() - }; - - // Get extra configuration details from cacheProvider's configuration for this diskstore - // Auto Expand - if( NOT structKeyExists(config, "autoExpandPath") ){ - config.autoExpandPath = true; - } - - // Check directory path - if( NOT structKeyExists(config,"directoryPath") ){ - throw(message="The 'directoryPath' configuration property was not found in the cache configuration", - detail="Please check the cache configuration and add the 'directoryPath' property. Current Configuration: #config.toString()#", - type="DiskStore.InvalidConfigurationException"); - } - - //AutoExpand - if( config.autoExpandPath ){ - instance.directoryPath = expandPath( config.directoryPath ); - } - else{ - instance.directoryPath = config.directoryPath; - } +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * @author Luis Majano + * + * I am a disk store, I am not that fancy as I am slower. + */ +component implements="wirebox.system.cache.store.IObjectStore" accessors="true"{ + + /** + * The cache provider reference + */ + property name="cacheProvider" doc_generic="wirebox.system.cache.providers.ICacheProvider"; + + /** + * The human store name + */ + property name="storeID"; + + /** + * The metadata indexer object + */ + property name="indexer" doc_generic="wirebox.system.cache.store.indexers.MetadataIndexer"; + + /** + * The object serializer and deserializer utility + */ + property name="converter" doc_generic="wirebox.system.core.conversion.ObjectMarshaller"; + + /** + * The location of the disk cache + */ + property name="directoryPath"; + + /** + * Constructor + * + * @cacheProvider The associated cache provider as wirebox.system.cache.providers.ICacheProvider + * @cacheprovider.doc_generic wirebox.system.cache.providers.ICacheProvider + */ + function init( required cacheProvider ){ + // Store Fields + var fields = "hits,timeout,lastAccessTimeout,created,LastAccessed,isExpired,isSimple"; + var config = arguments.cacheProvider.getConfiguration(); + + // Prepare instance + variables.cacheProvider = arguments.cacheProvider; + variables.storeID = createObject( 'java', 'java.lang.System' ).identityHashCode( this ); + variables.indexer = new wirebox.system.cache.store.indexers.MetadataIndexer( fields ); + variables.converter = new wirebox.system.core.conversion.ObjectMarshaller(); + variables.directoryPath = ""; + + // Get extra configuration details from cacheProvider's configuration for this diskstore + // Auto Expand + if( isNull( config.autoExpandPath ) ){ + config.autoExpandPath = true; + } + + // Check directory path + if( isNull( config.directoryPath ) ){ + throw( + message = "The 'directoryPath' configuration property was not found in the cache configuration", + detail = "Please check the cache configuration and add the 'directoryPath' property. Current Configuration: #config.toString()#", + type = "DiskStore.InvalidConfigurationException" + ); + } + + // AutoExpand + variables.directoryPath = ( config.autoExpandPath ? expandPath( config.directoryPath ) : config.directoryPath ); + + // Check if directory exists else create it + if( !directoryExists( variables.directoryPath ) ){ + directoryCreate( variables.directoryPath ); + } + + return this; + } + + /** + * Flush the store to a permanent storage + */ + void function flush(){ + return; + } + + /** + * Reap the storage + */ + void function reap(){ + return; + } + + /** + * Get the store's pool metadata indexer structure + * + * @return wirebox.system.cache.store.indexers.MetadataIndexer + */ + function getIndexer(){ + return variables.indexer; + } + + /** + * Clear all the elements in the store + */ + void function clearAll(){ + directoryDelete( variables.directoryPath, true ); + variables.indexer.clearAll(); + directoryCreate( variables.directoryPath ); + } + + /** + * Get all the store's object keys array + * + * @return array + */ + function getKeys(){ + return variables.indexer.getKeys(); + } + + /** + * Check if an object is in the store + * + * @objectKey The key to lookup + * + * @return boolean + */ + function lookup( required objectKey ){ + lock + name="DiskStore.#variables.storeID#.#arguments.objectKey#" + type="readonly" + timeout="10" + throwonTimeout="true" + { + var isFileOnDisk = fileExists( getCacheFilePath( arguments.objectKey ) ); - //Check if directory exists else create it - if( NOT directoryExists( instance.directoryPath ) ){ - directoryCreate( instance.directoryPath ); - } - - return this; - - - - - - - - - - - - - - - - - - - - - - directoryDelete( instance.directoryPath, true ); - instance.indexer.clearAll(); - directoryCreate( instance.directoryPath ); - - - - - - - - - - - - - - - - - - - // check if object is missing and in indexer - if( NOT fileExists( getCacheFilePath( arguments.objectKey ) ) AND instance.indexer.objectExists( arguments.objectKey ) ){ - instance.indexer.clear( arguments.objectKey ); + if( !isFileOnDisk AND variables.indexer.objectExists( arguments.objectKey ) ){ + variables.indexer.clear( arguments.objectKey ); return false; } // Check if object on disk, on indexer and NOT expired - if( fileExists( getCacheFilePath( arguments.objectKey ) ) - AND instance.indexer.objectExists( arguments.objectKey ) - AND NOT instance.indexer.getObjectMetadataProperty(arguments.objectKey,"isExpired") ){ + if( + isFileOnDisk AND + variables.indexer.objectExists( arguments.objectKey ) AND NOT + variables.indexer.getObjectMetadataProperty( arguments.objectKey, "isExpired" ) + ){ return true; } return false; - - - - - - - - - - - - if( lookup(arguments.objectKey) ){ + } + } + + /** + * Get an object from the store with metadata tracking, or null if not found + * + * @objectKey The key to retrieve + */ + function get( required objectKey ){ + lock + name="DiskStore.#variables.storeID#.#arguments.objectKey#" + type="exclusive" + timeout="10" + throwonTimeout="true" + { + if( lookup( arguments.objectKey ) ){ // Record Metadata Access - instance.indexer.setObjectMetadataProperty(arguments.objectKey,"hits", instance.indexer.getObjectMetadataProperty(arguments.objectKey,"hits")+1); - instance.indexer.setObjectMetadataProperty(arguments.objectKey,"LastAccessed", now()); + variables.indexer.setObjectMetadataProperty( + arguments.objectKey, + "hits", + variables.indexer.getObjectMetadataProperty( arguments.objectKey, "hits" ) + 1 + ); + variables.indexer.setObjectMetadataProperty( + arguments.objectKey, + "LastAccessed", + now() + ); + // Is resetTimeoutOnAccess enabled? If so, jump up the creation time to increase the timeout + if( variables.cacheProvider.getConfiguration().resetTimeoutOnAccess ){ + variables.indexer.setObjectMetadataProperty( + arguments.objectKey, + "created", + now() + ); + } return getQuiet( arguments.objectKey ); } - - - - - - - - - - - - - + } + } + + /** + * Get an object from cache with no metadata tracking + * + * @objectKey The key to retrieve + */ + function getQuiet( required objectKey ){ + lock + name="DiskStore.#variables.storeID#.#arguments.objectKey#" + type="exclusive" + timeout="10" + throwonTimeout="true" + { if( lookup( arguments.objectKey ) ){ + var thisFilePath = getCacheFilePath( arguments.objectKey ); // if simple value, just return it - if( instance.indexer.getObjectMetadataProperty( arguments.objectKey, "isSimple" ) ){ + if( variables.indexer.getObjectMetadataProperty( arguments.objectKey, "isSimple" ) ){ return trim( fileRead( thisFilePath ) ); } - //else we deserialize - return instance.converter.deserializeObject( filePath = thisFilePath ); - + // else we deserialize + return variables.converter.deserializeObject( filePath = thisFilePath ); } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + } + } + + /** + * Expire an object + * + * @objectKey The key to expire + */ + void function expireObject( required objectKey ){ + variables.indexer.setObjectMetadataProperty( + arguments.objectKey, + "isExpired", + true + ); + } + + /** + * Expire check + * + * @objectKey The key to check + * + * @return boolean + */ + function isExpired( required objectKey ){ + return variables.indexer.getObjectMetadataProperty( arguments.objectKey, "isExpired" ); + } + + /** + * Sets an object in the storage + * + * @objectKey The object key + * @object The object to save + * @timeout Timeout in minutes + * @lastAccessTimeout Idle Timeout in minutes + * @extras A map of extra name-value pairs to store alongside the object + */ + void function set( + required objectKey, + required object, + timeout="", + lastAccessTimeout="", + extras={} + ){ + var thisFilePath = getCacheFilePath( arguments.objectKey ); + var metaData = { + "hits" = 1, + "timeout" = arguments.timeout, + "lastAccessTimeout" = arguments.lastAccessTimeout, + "created" = now(), + "lastAccessed" = now(), + "isExpired" = false, + "isSimple" = true + }; + + lock + name="DiskStore.#variables.storeID#.#arguments.objectKey#" + type="exclusive" + timeout="10" + throwonTimeout="true" + { // If simple value just write it out to disk - if( isSimpleValue(arguments.object) ){ - fileWrite( thisFilePath, trim( arguments.object ) ); - } - else{ + if( isSimpleValue( arguments.object ) ){ + fileWrite( thisFilePath, trim( arguments.object ) ); + } else { // serialize it - instance.converter.serializeObject(arguments.object, thisFilePath); + variables.converter.serializeObject( arguments.object, thisFilePath ); metaData.isSimple = false; } // Save the object's metadata - instance.indexer.setObjectMetadata(arguments.objectKey, metaData); - - - - - - - - - - - - + variables.indexer.setObjectMetadata( arguments.objectKey, metaData ); + } + } + + /** + * Clears an object from the storage + * + * @objectKey The object key to clear + */ + function clear( required objectKey ){ + lock + name="DiskStore.#variables.storeID#.#arguments.objectKey#" + type="exclusive" + timeout="10" + throwonTimeout="true" + { + var thisFilePath = getCacheFilePath( arguments.objectKey ); // check it - if( NOT fileExists( thisFilePath ) ){ + if( !fileExists( thisFilePath ) ){ return false; } // Remove it fileDelete( thisFilePath ); - instance.indexer.clear( arguments.objectKey ); + variables.indexer.clear( arguments.objectKey ); return true; - - - - - - - - - - - - - - - - return instance.directoryPath & "/" & hash(arguments.objectKey) & ".cachebox"; - - - - - - - - - \ No newline at end of file + } + } + + /** + * Get the size of the store + */ + function getSize(){ + return variables.indexer.getSize(); + } + + //********************************* PRIVATE ************************************// + + /** + * Get the cached file path according to our rules + * + * @objectKey The key to lookup + */ + function getCacheFilePath( required objectKey ){ + return variables.directoryPath & "/" & hash( arguments.objectKey ) & ".cachebox"; + } + +} \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/store/IObjectStore.cfc b/src/cfml/system/wirebox/system/cache/store/IObjectStore.cfc index 9df88bf3f..e460faedb 100644 --- a/src/cfml/system/wirebox/system/cache/store/IObjectStore.cfc +++ b/src/cfml/system/wirebox/system/cache/store/IObjectStore.cfc @@ -1,78 +1,109 @@ - - + /** + * Reap the storage + */ + void function reap(); - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + /** + * Clear all the elements in the store + */ + void function clearAll(); - - - - - \ No newline at end of file + /** + * Get the store's pool metadata indexer structure + * + * @return wirebox.system.cache.store.indexers.MetadataIndexer + */ + function getIndexer(); + + /** + * Get all the store's object keys array + * + * @return array + */ + function getKeys(); + + /** + * Check if an object is in the store + * + * @objectKey The key to lookup + * + * @return boolean + */ + function lookup( required objectKey ); + + /** + * Get an object from the store with metadata tracking + * + * @objectKey The key to retrieve + */ + function get( required objectKey ); + + /** + * Get an object from cache with no metadata tracking + * + * @objectKey The key to retrieve + */ + function getQuiet( required objectKey ); + + /** + * Expire an object + * + * @objectKey The key to expire + */ + void function expireObject( required objectKey ); + + /** + * Expire check + * + * @objectKey The key to check + * + * @return boolean + */ + function isExpired( required objectKey ); + + /** + * Sets an object in the storage + * + * @objectKey The object key + * @object The object to save + * @timeout Timeout in minutes + * @lastAccessTimeout Idle Timeout in minutes + * @extras A map of extra name-value pairs to store alongside the object + */ + void function set( + required objectKey, + required object, + timeout, + lastAccessTimeout, + extras + ); + + /** + * Clears an object from the storage + * + * @objectKey The object key to clear + */ + function clear( required objectKey ); + + /** + * Get the size of the store + */ + function getSize(); + +} \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/store/JDBCStore.cfc b/src/cfml/system/wirebox/system/cache/store/JDBCStore.cfc index 3b6093ab7..23a7888d4 100644 --- a/src/cfml/system/wirebox/system/cache/store/JDBCStore.cfc +++ b/src/cfml/system/wirebox/system/cache/store/JDBCStore.cfc @@ -1,442 +1,558 @@ - - - - - - - - - - // Store Fields - var fields = "objectKey,hits,timeout,lastAccessTimeout,created,lastAccessed,isExpired,isSimple"; - var config = arguments.cacheProvider.getConfiguration(); - - // Prepare instance - instance = { - storeID = createObject('java','java.lang.System').identityHashCode( this ), - cacheProvider = arguments.cacheProvider, - converter = createObject("component","wirebox.system.core.conversion.ObjectMarshaller").init() - }; - - // Get Extra config data - instance.dsn = config.dsn; - instance.table = config.table; - - // Check credentials - if( NOT structKeyExists(config, "dsnUsername") ){ - config.dsnUsername = ""; +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * @author Luis Majano + * + * I am a cool cool JDBC Store for CacheBox + * You need to create the table first with the following columns + * + * id - varchar(100) PK + * objectKey - varchar(255) + * objectValue - clob, longtext, etc + * hits - integer + * timeout - integer + * lastAccessTimeout - integer + * created - datetime or timestamp + * lastAccessed - datetime or timestamp + * isExpired - tinyint or boolean + * isSimple - tinyint or boolean + * + * We also recommend indexes for: hits, created, lastAccessed, timeout and isExpired columns. + * + * Or look in the /wirebox/system/cache/store/sql/*.sql for you sql script for your DB. + */ +component implements="wirebox.system.cache.store.IObjectStore" accessors="true"{ + + /** + * The cache provider reference + */ + property name="cacheProvider" doc_generic="wirebox.system.cache.providers.ICacheProvider"; + + /** + * The human store name + */ + property name="storeID"; + + /** + * The metadata indexer object + */ + property name="indexer" doc_generic="wirebox.system.cache.store.indexers.MetadataIndexer"; + + /** + * The object serializer and deserializer utility + */ + property name="converter" doc_generic="wirebox.system.core.conversion.ObjectMarshaller"; + + /** + * The datasource to use for the connection + */ + property name="dsn"; + /** + * The table to use for storage + */ + property name="table"; + /** + * The username to use for the connection, if any + */ + property name="dsnUsername"; + /** + * The password to use for the connection, if any + */ + property name="dsnPassword"; + /** + * Auto create the table or just use it + */ + property name="tableAutoCreate" type="boolean" default="true"; + + /** + * Constructor + * + * @cacheProvider The associated cache provider as wirebox.system.cache.providers.ICacheProvider + * @cacheprovider.doc_generic wirebox.system.cache.providers.ICacheProvider + */ + function init( required cacheProvider ){ + // Store Fields + var fields = "objectKey,hits,timeout,lastAccessTimeout,created,lastAccessed,isExpired,isSimple"; + var config = arguments.cacheProvider.getConfiguration(); + + // Prepare instance + variables.cacheProvider = arguments.cacheProvider; + variables.storeID = createObject( 'java', 'java.lang.System' ).identityHashCode( this ); + variables.converter = new wirebox.system.core.conversion.ObjectMarshaller(); + variables.indexer = new wirebox.system.cache.store.indexers.JDBCMetadataIndexer( fields, config, this ); + + // Get Extra config data + variables.dsn = config.dsn; + variables.table = config.table; + + // Check credentials + if( isNull( config.dsnUsername ) ){ + config.dsnUsername = ""; + } + if( isNull( config.dsnPassword ) ){ + config.dsnPassword = ""; + } + variables.dsnUsername = config.dsnUsername; + variables.dsnPassword = config.dsnPassword; + + // Check autoCreate + if( isNull( config.tableAutoCreate ) ){ + config.tableAutoCreate = true; + } + variables.tableAutoCreate = config.tableAutoCreate; + + // ensure the table + if( variables.tableAutoCreate ){ + ensureTable(); + } + + variables.isLucee = server.keyExists( "lucee" ); + + return this; + } + + /** + * Flush the store to a permanent storage + */ + void function flush(){ + return; + } + + /** + * Reap the storage + */ + void function reap(){ + return; + } + + /** + * Get the store's pool metadata indexer structure + * + * @return wirebox.system.cache.store.indexers.MetadataIndexer + */ + function getIndexer(){ + return variables.indexer; + } + + /** + * Clear all the elements in the store + */ + void function clearAll(){ + queryExecute( + "TRUNCATE TABLE #variables.table#", + {}, + { + datsource = variables.dsn, + username = variables.dsnUsername, + password = variables.dsnPassword } - if( NOT structKeyExists(config, "dsnPassword") ){ - config.dsnPassword = ""; - } - instance.dsnUsername = config.dsnUsername; - instance.dsnPassword = config.dsnPassword; - - // Check autoCreate - if( NOT structKeyExists(config, "tableAutoCreate") ){ - config.tableAutoCreate = true; - } - instance.tableAutoCreate = config.tableAutoCreate; - - // ensure the table - if( config.tableAutoCreate ){ - ensureTable(); - } - - // Indexer - instance.indexer = createObject("component","wirebox.system.cache.store.indexers.JDBCMetadataIndexer").init(fields, config, this); - - return this; - - - - - - - - - - - - - - - - - - - - - - - - TRUNCATE TABLE #instance.table# - - - - - - - - - - - - - - - SELECT objectKey - FROM #instance.table# - ORDER BY objectKey ASC - - - - - - - - - - - - - - - SELECT id, isExpired - FROM #instance.table# - WHERE id = - - - - - - - - - - - var q = lookupQuery( arguments.objectKey ); - - // Check if object in pool - if( q.recordCount AND NOT q.isExpired){ - return true; + ); + } + + /** + * Get all the store's object keys array + * + * @return array + */ + function getKeys(){ + var qResults = queryExecute( + "SELECT objectKey FROM #variables.table# ORDER BY objectKey ASC", + {}, + { + datsource = variables.dsn, + username = variables.dsnUsername, + password = variables.dsnPassword } + ); + + return ( + variables.isLucee ? + queryColumnData( qResults, "objectKey" ) : + listToArray( valueList( qResults.objectKey ) ) + ); + } + + /** + * Check if an object is in the store + * + * @objectKey The key to lookup + * + * @return boolean + */ + function lookup( required objectKey ){ + var q = lookupQuery( arguments.objectKey ); + return ( q.recordCount AND NOT q.isExpired ? true : false ); + } + + /** + * Get an object from the store with metadata tracking, or null if not found + * + * @objectKey The key to retrieve + */ + function get( required objectKey ){ + var normalizedID = getNormalizedID( arguments.objectKey ); + + transaction{ + // select entry + var q = queryExecute( + "SELECT * + FROM #variables.table# + WHERE id = ? + ", + [ normalizedID ], + { + datsource = variables.dsn, + username = variables.dsnUsername, + password = variables.dsnPassword + } + ); - return false; - - - - - - - - - - - - - - - - SELECT * - FROM #instance.table# - WHERE id = - - - - - - UPDATE #instance.table# - SET lastAccessed = , - hits = hits + 1 - WHERE id = - - - - - - - // Just return if records found, else null + // Update stats if found if( q.recordCount ){ - - // if simple value, just return it - if( q.isSimple ){ - return q.objectValue; + // Setup SQL + var targetSql = "UPDATE #variables.table# + SET lastAccessed = :lastAccessed, + hits = hits + 1 + WHERE id = :id"; + + // Is resetTimeoutOnAccess enabled? If so, jump up the creation time to increase the timeout + if( variables.cacheProvider.getConfiguration().resetTimeoutOnAccess ){ + var targetSql = "UPDATE #variables.table# + SET lastAccessed = :lastAccessed, + hits = hits + 1, + created = :created + WHERE id = :id"; } - //else we return deserialized - return instance.converter.deserializeObject(binaryObject=q.objectValue); + var qStats = queryExecute( + "#targetSQL#", + { + lastAccessed : { value="#now()#", cfsqltype="timestamp" }, + id : { value="#normalizedID#", cfsqltype="varchar" }, + created : { value="#now()#", cfsqltype="timestamp" } + }, + { + datsource = variables.dsn, + username = variables.dsnUsername, + password = variables.dsnPassword + } + ); + } + } // end transaction + + // Just return if records found, else null + if( q.recordCount ){ + return ( q.isSimple ? q.objectValue : variables.converter.deserializeObject( binaryObject=q.objectValue ) ); + } + } + + /** + * Get an object from cache with no metadata tracking + * + * @objectKey The key to retrieve + */ + function getQuiet( required objectKey ){ + // select entry + var q = queryExecute( + "SELECT * + FROM #variables.table# + WHERE id = ? + ", + [ getNormalizedID( arguments.objectKey ) ], + { + datsource = variables.dsn, + username = variables.dsnUsername, + password = variables.dsnPassword + } + ); + + // Just return if records found, else null + if( q.recordCount ){ + return ( q.isSimple ? q.objectValue : variables.converter.deserializeObject( binaryObject=q.objectValue ) ); + } + } + + /** + * Expire an object + * + * @objectKey The key to expire + */ + void function expireObject( required objectKey ){ + // select entry + var q = queryExecute( + "UPDATE #variables.table# + SET isExpired = ? + WHERE id = ? + ", + [ 1, getNormalizedID( arguments.objectKey ) ], + { + datsource = variables.dsn, + username = variables.dsnUsername, + password = variables.dsnPassword + } + ); + } + + /** + * Expire check + * + * @objectKey The key to check + * + * @return boolean + */ + function isExpired( required objectKey ){ + // select entry + var q = queryExecute( + "SELECT isExpired + FROM #variables.table# + WHERE id = ? + ", + [ getNormalizedID( arguments.objectKey ) ], + { + datsource = variables.dsn, + username = variables.dsnUsername, + password = variables.dsnPassword + } + ); + + return ( q.recordCount && q.isExpired ? true : false ); + } + + /** + * Sets an object in the storage + * + * @objectKey The object key + * @object The object to save + * @timeout Timeout in minutes + * @lastAccessTimeout Idle Timeout in minutes + * @extras A map of extra name-value pairs to store alongside the object + */ + void function set( + required objectKey, + required object, + timeout="0", + lastAccessTimeout="0", + extras={} + ){ + var normalizedId = getNormalizedID( arguments.objectKey ); + var isSimple = true; + + // Test if not simple to serialize + if( !isSimpleValue( arguments.object ) ){ + isSimple = false; + arguments.object = variables.converter.serializeObject( arguments.object ); + } + + transaction{ + if( !lookupQuery( arguments.objectKey ).recordCount ){ + var q = queryExecute( + "INSERT INTO #variables.table# (id,objectKey,objectValue,hits,timeout,lastAccessTimeout,created,lastAccessed,isExpired,isSimple) + VALUES ( + :id, + :objectKey, + :objectValue, + :hits, + :timeout, + :lastAccessTimeout, + :now, + :now, + :isExpired, + :isSimple + ) + ", + { + id = { value="#normalizedId#", cfsqltype="varchar" }, + objectKey = { value="#arguments.objectKey#", cfsqltype="varchar" }, + objectValue = { value="#arguments.object#", cfsqltype="longvarchar" }, + hits = { value="1", cfsqltype="integer" }, + timeout = { value="#arguments.timeout#", cfsqltype="integer" }, + lastAccessTimeout = { value="#arguments.lastAccessTimeout#", cfsqltype="integer" }, + now = { value=now(), cfsqltype="timestamp" }, + now = { value=now(), cfsqltype="timestamp" }, + isExpired = { value="0", cfsqltype="bit" }, + isSimple = { value="#isSimple#", cfsqltype="bit" } + }, + { + datsource = variables.dsn, + username = variables.dsnUsername, + password = variables.dsnPassword + } + ); + + return; } - - - - - - - - - - - - - SELECT * - FROM #instance.table# - WHERE id = - - - - // Just return if records found, else null - if( q.recordCount ){ - // if simple value, just return it - if( q.isSimple ){ - return q.objectValue; + var q = queryExecute( + "UPDATE #variables.table# + SET objectKey = :objectKey, + objectValue = :objectValue, + hits = :hits, + timeout = :timeout, + lastAccessTimeout = :lastAccessTimeout, + created = :now, + lastAccessed = :now, + isExpired = :isExpired, + isSimple = :isSimple + WHERE id = :id + ", + { + id = { value="#normalizedId#", cfsqltype="varchar" }, + objectKey = { value="#arguments.objectKey#", cfsqltype="varchar" }, + objectValue = { value="#arguments.object#", cfsqltype="longvarchar" }, + hits = { value="1", cfsqltype="integer" }, + timeout = { value="#arguments.timeout#", cfsqltype="integer" }, + lastAccessTimeout = { value="#arguments.lastAccessTimeout#", cfsqltype="integer" }, + now = { value=now(), cfsqltype="timestamp" }, + now = { value=now(), cfsqltype="timestamp" }, + isExpired = { value="0", cfsqltype="bit" }, + isSimple = { value="#isSimple#", cfsqltype="bit" } + }, + { + datsource = variables.dsn, + username = variables.dsnUsername, + password = variables.dsnPassword } - - //else we return deserialized - return instance.converter.deserializeObject(binaryObject=q.objectValue); + ); + } + } + + /** + * Clears an object from the storage + * + * @objectKey The object key to clear + */ + function clear( required objectKey ){ + queryExecute( + "DELETE + FROM #variables.table# + WHERE id = ? + ", + [ getNormalizedID( arguments.objectKey ) ], + { + datsource = variables.dsn, + username = variables.dsnUsername, + password = variables.dsnPassword, + result = "local.q" + } + ); + + return ( q.recordCount ? true : false ); + } + + /** + * Get the size of the store + */ + function getSize(){ + var q = queryExecute( + "SELECT count( id ) as totalCount + FROM #variables.table# + ", + {}, + { + datsource = variables.dsn, + username = variables.dsnUsername, + password = variables.dsnPassword + } + ); + + return q.totalCount; + } + + /** + * Get the cached normalized id as we store it + * + * @objectKey The object key + */ + function getNormalizedId( required objectKey ){ + return hash( arguments.objectKey ); + } + + //********************************* PRIVATE ************************************// + + /** + * Get the id and isExpired from the object + * + * @objectKey The key of the object + */ + private query function lookupQuery( required objectKey ){ + return queryExecute( + "SELECT id, isExpired + FROM #variables.table# + WHERE id = ? + ", + [ getNormalizedID( arguments.objectKey ) ], + { + datsource = variables.dsn, + username = variables.dsnUsername, + password = variables.dsnPassword + } + ); + } + + /** + * Create the caching table if necessary + */ + private function ensureTable(){ + var qCreate = ""; + var tableFound = false; + var create = { + afterCreate = "", + afterLastProperty = "" + }; + + cfdbinfo( datasource="#variables.dsn#", name="local.qDBInfo", type="version" ); + + // Get Tables on this DSN + cfdbinfo( datasource="#variables.dsn#", name="local.qTables", type="tables" ); + + // Choose Text Type + switch( qDBInfo.database_productName ){ + case "PostgreSQL" : { + create.valueType = "text"; + create.timeType = "timestamp"; + create.intType = "integer"; + create.booleanType = "boolean"; + break; } - - - - - - - - - - - - UPDATE #instance.table# - SET isExpired = - WHERE id = - - - - - - - - - - - - - - SELECT isExpired - FROM #instance.table# - WHERE id = - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - INSERT INTO #instance.table# (id,objectKey,objectValue,hits,timeout,lastAccessTimeout,created,lastAccessed,isExpired,isSimple) - VALUES ( - , - , - , - , - , - , - , - , - , - - ) - - - - - - - - UPDATE #instance.table# - SET objectKey = , - objectValue = , - hits = , - timeout = , - lastAccessTimeout = , - created = , - lastAccessed = , - isExpired = , - isSimple = - WHERE id = - - - - - - - - - - - - - - - - - - - - DELETE - FROM #instance.table# - WHERE id = - - - - - - - - - - - - SELECT count(id) as totalCount - FROM #instance.table# - - - - - - - - - - return hash( arguments.objectKey ); - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CREATE TABLE #instance.table# ( + case "MySQL" : { + create.valueType = "longtext"; + create.afterCreate = "ENGINE=InnoDB DEFAULT CHARSET=utf8"; + create.timeType = "datetime"; + create.intType = "int"; + create.booleanType = "tinyint"; + create.afterLastProperty = "INDEX `hits` (`hits`),INDEX `created` (`created`),INDEX `lastAccessed` (`lastAccessed`),INDEX `timeout` (`timeout`),INDEX `isExpired` (`isExpired`)"; + break; + } + case "Microsoft SQL Server" : { + create.valueType = "ntext"; + create.timeType = "datetime"; + create.intType = "int"; + create.booleanType = "tinyint"; + break; + } + case "Oracle" : { + create.valueType = "clob"; + create.timeType = "timestamp"; + create.intType = "int"; + create.booleanType = "boolean"; + break; + } + default : { + create.valueType = "text"; + create.timeType = "timestamp"; + create.intType = "integer"; + create.booleanType = "tinyint"; + break; + } + } + + if( + listToArray( valueList( qTables.table_name ) ) + .findNoCase( variables.table ) == 0 + ){ + queryExecute( + "CREATE TABLE #variables.table# ( id VARCHAR(100) NOT NULL, objectKey VARCHAR(255) NOT NULL, objectValue #create.valueType# NOT NULL, @@ -449,8 +565,15 @@ Or look in the /wirebox/system/cache/store/sql/*.sql for you sql script for your isSimple #create.booleanType# NOT NULL DEFAULT '0', PRIMARY KEY (id) ) #create.afterCreate# - - - + ", + {}, + { + datsource = variables.dsn, + username = variables.dsnUsername, + password = variables.dsnPassword + } + ); + } + } - \ No newline at end of file +} \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/store/indexers/JDBCMetadataIndexer.cfc b/src/cfml/system/wirebox/system/cache/store/indexers/JDBCMetadataIndexer.cfc index 70efa8e1e..4c02668f3 100644 --- a/src/cfml/system/wirebox/system/cache/store/indexers/JDBCMetadataIndexer.cfc +++ b/src/cfml/system/wirebox/system/cache/store/indexers/JDBCMetadataIndexer.cfc @@ -1,191 +1,205 @@ - - - - - - - - - - - - - - - super.init(arguments.fields); - - // store db sql compatibility type - instance.sqlType = "MySQL"; - if( findNoCase("Microsoft SQL", DBData.database_productName) ){ - instance.sqlType = "MSSQL"; - } - - // store jdbc configuration - instance.config = arguments.config; - - // store storage reference - instance.store = arguments.store; - - return this; - - - - - - - - - - - - - - - // Normalize fields - if( isArray(arguments.fields) ){ - arguments.fields = arrayToList( arguments.fields ); +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * @author Luis Majano + * + * This is a utility object that helps object stores keep their elements indexed + * and stored nicely. It is also a nice way to give back metadata results. + */ +component extends="wirebox.system.cache.store.indexers.MetadataIndexer" accessors="true"{ + + /** + * The SQL Type: Defaults to MySQL + */ + property name="sqlType"; + + /** + * The jdbc config structure + */ + property name="config" type="struct"; + + /** + * The JDBC Store we connect to + */ + property name="store" type="wirebox.system.cache.store.indexers.JDBCMetadataIndexer"; + + /** + * Constructor + * + * @fields The list or array of fields to bind this index on + * @config JDBC Configuration structure + * @store The associated storage + */ + function init( required fields, required struct config, required store ){ + + // Super init + super.init( arguments.fields ); + + // Get db data + cfdbinfo( type="version", datasource="#arguments.config.dsn#", name="local.DBData" ); + + // store db sql compatibility type: used mostly for pagination + variables.sqlType = ( findNoCase( "Microsoft SQL", DBData.database_productName ) ? "MSSQL" : "MySQL" ); + + // store jdbc configuration + variables.config = arguments.config; + + // store storage reference + variables.store = arguments.store; + + variables.isLucee = server.keyExists( "lucee" ); + + return this; + } + + /** + * Check if the metadata entry exists for an object + * + * @objectKey The key to get + */ + boolean function objectExists( required objectKey ){ + return queryExecute( + "SELECT id + FROM #variables.config.table# + WHERE id = ?", + [ variables.store.getNormalizedID( arguments.objectKey ) ], + { + datasource : variables.config.dsn, + username : variables.config.dsnUsername, + password : variables.config.dsnPassword + } + ).recordCount eq 1; + } + + /** + * Get the top 100 pool of metadata elements + */ + boolean function getPoolMetadata( numeric max = 100 ){ + var results = {}; + + // MySQL Default + var sql = "SELECT #variables.fields# FROM #variables.config.table# ORDER BY objectKey LIMIT ?"; + // MSSQL + if( variables.sqlType == "MSSQL" ){ + sql = "SELECT TOP ? #variables.fields# FROM #variables.config.table# ORDER BY objectKey"; + } + + queryExecute( + sql, + [ arguments.max ], + { + datasource : variables.config.dsn, + username : variables.config.dsnUsername, + password : variables.config.dsnPassword + } + ).each( function( row ){ + results[ row.objectKey ] = { + hits = row.hits, + timeout = row.timeout, + lastAccessTimeout = row.lastAccessTimeout, + created = row.created, + LastAccessed = row.lastAccessed, + isExpired = row.isExpired, + isSimple = row.isSimple + }; + } ); + + return results; + } + + /** + * Get a metadata entry for a specific entry. Exception if key not found + * + * @objectKey The key to get + */ + struct function getObjectMetadata( required objectKey ){ + var qData = queryExecute( + "SELECT #variables.fields# + FROM #variables.config.table# + WHERE id = ?", + [ variables.store.getNormalizedID( arguments.objectKey ) ], + { + datasource : variables.config.dsn, + username : variables.config.dsnUsername, + password : variables.config.dsnPassword } - - // Store fields - instance.fields = arguments.fields; - - - - - - - - - - - - - SELECT id - FROM #instance.config.table# - WHERE id = - - - - - - - - - - - - - - SELECT TOP 100 #instance.fields# - FROM #instance.config.table# - ORDER BY objectKey - LIMIT 100 - - - - - - - - - - - - - - - - - - - - - SELECT #instance.fields# - FROM #instance.config.table# - WHERE id = - - - - - - - - - - - - - - - - - - - - - SELECT #arguments.property# as prop - FROM #instance.config.table# - WHERE id = - - - - - - - - - - - - - - - - - - - - - SELECT id, objectKey - FROM #instance.config.table# - ORDER BY #arguments.property# #arguments.sortOrder# - - - - - - - - - - - - - - - - - \ No newline at end of file + ); + + return variables.fields.listReduce( function( accumulator, target ){ + accumulator[ target ] = qData[ target ]; + return accumulator; + }, {} ); + } + + /** + * Get a metadata entry for a specific entry. Exception if key not found + * + * @objectKey The key to get + * @property The metadata property to get + * @defaultValue The default value if property doesn't exist + */ + function getObjectMetadataProperty( required objectKey, required property, defaultValue ){ + var metadata = queryExecute( + "SELECT #variables.fields# + FROM #variables.config.table# + WHERE id = ?", + [ variables.store.getNormalizedID( arguments.objectKey ) ], + { + datasource : variables.config.dsn, + username : variables.config.dsnUsername, + password : variables.config.dsnPassword + } + ); + + if( structKeyExists( metadata, arguments.property ) ){ + return metadata[ arguments.property ]; + } + + if( !isNull( arguments.defaultValue ) ){ + return arguments.defaultValue; + } + + throw( + type = "InvalidProperty", + message = "Invalid property requested: #arguments.property#", + detail = "Valid properties are: #variables.fields#" + ); + } + + /** + * Get the size of the store + */ + numeric function getSize(){ + // Delegate to storage + return variables.store.size(); + } + + /** + * Get an array of sorted keys for this indexer according to parameters + * + * @objectKey + * @property + * @value + */ + array function getSortedKeys( required property, sortType="text", sortOrder="asc" ){ + var qResults = queryExecute( + "SELECT id, objectKey + FROM #variables.config.table# + ORDER BY #arguments.property# #arguments.sortOrder#", + [ variables.store.getNormalizedID( arguments.objectKey ) ], + { + datasource : variables.config.dsn, + username : variables.config.dsnUsername, + password : variables.config.dsnPassword + } + ); + + return ( + variables.isLucee ? + queryColumnData( qResults, "objectKey" ) : + listToArray( valueList( qResults.objectKey ) ) + ); + } + +} \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/store/indexers/MetadataIndexer.cfc b/src/cfml/system/wirebox/system/cache/store/indexers/MetadataIndexer.cfc index 5e935cb8e..e36966002 100644 --- a/src/cfml/system/wirebox/system/cache/store/indexers/MetadataIndexer.cfc +++ b/src/cfml/system/wirebox/system/cache/store/indexers/MetadataIndexer.cfc @@ -1,146 +1,177 @@ - - - - - - - - - instance = { - // Create metadata pool - poolMetadata = CreateObject("java","java.util.concurrent.ConcurrentHashMap").init(), - // Index ID - indexID = createObject('java','java.lang.System').identityHashCode(this) - }; - - // Store Fields - setFields( arguments.fields ); - - return this; - - - - - - - - - - - - - - - // Normalize fields - if( isArray(arguments.fields) ){ - arguments.fields = arrayToList( arguments.fields ); - } - - // Store fields - instance.fields = arguments.fields; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - validateField( arguments.property ); - - return structSort( instance.poolMetadata, arguments.sortType, arguments.sortOrder, arguments.property ); - - - - - - - - - - - - - - \ No newline at end of file +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * @author Luis Majano + * + * This is a utility object that helps object stores keep their elements indexed and stored nicely. + * It is also a nice way to give back metadata results. + */ +component accessors="true"{ + + /** + * The metadata pool + */ + property name="poolMetadata" doc_generic="java.util.concurrent.ConcurrentHashMap"; + + /** + * The human id of this indexer + */ + property name="indexID"; + + /** + * The fields this indexer is tracking + */ + property name="fields"; + + /** + * Constructor + * + * @fields The list or array of fields to bind this index on + * + */ + function init( required fields ){ + // Create metadata pool + variables.poolMetadata = createObject( "java","java.util.concurrent.ConcurrentHashMap" ).init(); + // Index ID + variables.indexID = createObject( "java", "java.lang.System").identityHashCode( this ); + // Collections (for static .list() method) + variables.collections = createObject( "java", "java.util.Collections" ); + + setFields( arguments.fields ); + + return this; + } + + /** + * Fancy setter for fields + * + * @fields The fields list or array + */ + MetadataIndexer function setFields( required fields ){ + if( isArray( arguments.fields ) ){ + arguments.fields = arrayToList( arguments.fields ); + } + variables.fields = arguments.fields; + + return this; + } + + /** + * Clear all the elements in the store + */ + MetadataIndexer function clearAll(){ + variables.poolMetadata.clear(); + return this; + } + + /** + * Clears an object from the storage + * + * @objectKey The object key to clear + */ + MetadataIndexer function clear( required objectKey ){ + variables.poolMetadata.remove( arguments.objectKey ); + return this; + } + + /** + * Get all the store's object keys array + * + * @return array + */ + array function getKeys(){ + return variables.collections.list( variables.poolMetadata.keys() ); + } + + /** + * Get a metadata entry for a specific entry. Exception if key not found + * + * @objectKey The key to get + */ + struct function getObjectMetadata( required objectKey ){ + return variables.poolMetadata.get( arguments.objectKey ); + } + + /** + * Set the metadata entry for a specific entry + * + * @objectKey The key to get + * @metadata The metadata struct to store + */ + MetadataIndexer function setObjectMetadata( required objectKey, required struct metadata ){ + variables.poolMetadata.put( arguments.objectKey, arguments.metadata ); + return this; + } + + /** + * Check if the metadata entry exists for an object + * + * @objectKey The key to get + */ + boolean function objectExists( required objectKey ){ + return variables.poolMetadata.containsKey( arguments.objectKey ); + } + + /** + * Get a metadata entry for a specific entry. Exception if key not found + * + * @objectKey The key to get + * @property The metadata property to get + * @defaultValue The default value if property doesn't exist + */ + function getObjectMetadataProperty( required objectKey, required property, defaultValue ){ + var metadata = getObjectMetadata( arguments.objectKey ); + + if( metadata.keyExists( arguments.property ) ){ + return metadata[ arguments.property ]; + } + + if( !isNull( arguments.defaultValue ) ){ + return arguments.defaultValue; + } + + throw( + type = "InvalidProperty", + message = "Invalid property requested: #arguments.property#", + detail = "Valid properties are: #structKeyList( metadata )#" + ); + } + + /** + * Set a metadata property for a specific entry + * + * @objectKey The key to set + * @property The metadata property to set + * @value The value to set + */ + MetadataIndexer function setObjectMetadataProperty( required objectKey, required property, required value ){ + getObjectMetadata( arguments.objectKey ) + .insert( arguments.property, arguments.value, true ); + return this; + } + + /** + * Get the size of the store + */ + numeric function getSize(){ + return variables.poolMetadata.size(); + } + + /** + * Get an array of sorted keys for this indexer according to parameters + * + * @objectKey + * @property + * @value + */ + array function getSortedKeys( required property, sortType="text", sortOrder="asc" ){ + return structSort( + variables.poolMetadata, + arguments.sortType, + arguments.sortOrder, + arguments.property + ); + } + +} \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/util/CacheStats.cfc b/src/cfml/system/wirebox/system/cache/util/CacheStats.cfc index a15db9a41..6377154ae 100644 --- a/src/cfml/system/wirebox/system/cache/util/CacheStats.cfc +++ b/src/cfml/system/wirebox/system/cache/util/CacheStats.cfc @@ -1,131 +1,174 @@ - - - - - - - - - instance = { - cacheProvider = arguments.cacheProvider - }; - - // Init reap to right now - setLastReapDateTime(now()); - - // Clear the stats to start fresh. - clearStatistics(); - - return this; - - - - - - - - - - - - - - var requests = instance.hits + instance.misses; - - if ( requests eq 0){ - return 0; - } - - return (instance.hits/requests) * 100; - - - - - - - - - - - - instance.hits = 0; - instance.misses = 0; - instance.evictionCount = 0; - instance.garbageCollections = 0; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - instance.evictionCount++; - - - - - - - instance.garbageCollections++; - - - - - - - instance.hits++; - - - - - - - instance.misses++; - - - - - - - - - \ No newline at end of file +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * @author Luis Majano + * + * This is a cache statistics object. We do not use internal method calls but leverage the properties directly so it is faster. + */ +component implements="wirebox.system.cache.util.IStats" accessors="true"{ + + /** + * The associated cache manager/provider of type: wirebox.system.cache.providers.ICacheProvider + */ + property name="cacheProvider" doc_generic="wirebox.system.cache.providers.ICacheProvider"; + + /** + * Recording of last reap + */ + property name="lastReapDateTime"; + /** + * Cache hits + */ + property name="hits" default="0"; + /** + * Cache misses + */ + property name="misses" default="0"; + /** + * Eviction counts + */ + property name="evictionCount" default="0"; + /** + * Garbage collection counts + */ + property name="garbageCollections" default="0"; + + /** + * Constructor + * + * @cacheProvider The associated cache manager/provider of type: wirebox.system.cache.providers.ICacheProvider + * @cacheProvider.doc_generic wirebox.system.cache.providers.ICacheProvider + */ + function init( required cacheProvider ){ + + variables.cacheProvider = arguments.cacheProvider; + // Clear the stats to start fresh. + clearStatistics(); + + return this; + } + + /** + * Get the associated cache provider/manager of type: wirebox.system.cache.providers.ICacheProvider + * + * @return wirebox.system.cache.providers.ICacheProvider + */ + function getAssociatedCache(){ + return variables.cacheProvider; + } + + /** + * Get the cache's performance ratio + */ + numeric function getCachePerformanceRatio(){ + var requests = variables.hits + variables.misses; + + if ( requests eq 0 ){ + return 0; + } + + return ( variables.hits / requests ) * 100; + } + + /** + * Get the associated cache's live object count + */ + numeric function getObjectCount(){ + return getAssociatedCache().getSize(); + } + + /** + * Clear the stats + * + * @return IStats + */ + function clearStatistics(){ + variables.lastReapDatetime = now(); + variables.hits = 0; + variables.misses = 0; + variables.evictionCount = 0; + variables.garbageCollections = 0; + + return this; + } + + /** + * Get the total cache's garbage collections + */ + numeric function getGarbageCollections(){ + return variables.garbageCollections; + } + + /** + * Get the total cache's eviction count + */ + numeric function getEvictionCount(){ + return variables.evictionCount; + } + + /** + * Get the total cache's hits + */ + numeric function getHits(){ + return variables.hits; + } + + /** + * Get the total cache's misses + */ + numeric function getMisses(){ + return variables.misses; + } + + /** + * Get the date/time of the last reap the cache did + * + * @return date/time or empty + */ + function getLastReapDatetime(){ + return variables.lastReapDatetime; + } + + /** + * Record an eviction hit + */ + CacheStats function evictionHit(){ + variables.evictionCount++; + return this; + } + + /** + * Record an garbage collection hit + */ + CacheStats function GCHit(){ + variables.garbageCollections++; + return this; + } + + /** + * Record an cache hit + */ + CacheStats function hit(){ + variables.hits++; + return this; + } + + /** + * Record an cache miss + */ + CacheStats function miss(){ + variables.misses++; + return this; + } + + /** + * A quick snapshot of the stats state + */ + struct function getMemento(){ + return variables.filter( function( k, v ){ + return ( !isCustomFunction( v ) && !isObject( v ) ); + } ); + } + +} \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/util/ElementCleaner.cfc b/src/cfml/system/wirebox/system/cache/util/ElementCleaner.cfc index 9e02138e7..f0c3b3afd 100644 --- a/src/cfml/system/wirebox/system/cache/util/ElementCleaner.cfc +++ b/src/cfml/system/wirebox/system/cache/util/ElementCleaner.cfc @@ -11,22 +11,22 @@ Description : - + - + variables.cacheProvider = arguments.cacheProvider; return this; - + - + - + - + @@ -39,14 +39,14 @@ Description : var x = 1; var tester = 0; var thisKey = ""; - + // sort array arraySort(cacheKeys, "textnocase"); - + for(x=1; x lte cacheKeysLength; x++){ // Get List Value thisKey = cacheKeys[x]; - + // Using Regex if( arguments.regex ){ tester = refindnocase( arguments.keySnippet, thisKey ); @@ -54,7 +54,7 @@ Description : else{ tester = findnocase( arguments.keySnippet, thisKey ); } - + // Test Evaluation if ( tester ){ getAssociatedCache().clear( thisKey ); @@ -62,7 +62,7 @@ Description : }
- + @@ -70,7 +70,7 @@ Description : //.*- = the cache suffix and appendages for regex to match var cacheKey = getAssociatedCache().getEventCacheKeyPrefix() & replace( arguments.eventsnippet, ".", "\.", "all" ) & ".*-.*"; - + //Check if we are purging with query string if( len( arguments.queryString ) neq 0 ){ cacheKey &= "-" & getAssociatedCache().getEventURLFacade().buildHash( arguments.queryString ); @@ -80,7 +80,7 @@ Description : clearByKeySnippet( keySnippet=cacheKey, regex=true ); - + @@ -91,40 +91,40 @@ Description : var cacheKey = ""; var keyPrefix = getAssociatedCache().getEventCacheKeyPrefix(); var eventURLFacade = getAssociatedCache().getEventURLFacade(); - + // normalize snippets if( isArray(arguments.eventSnippets) ){ arguments.eventsnippets = arrayToList( arguments.eventsnippets ); } - + // Loop on the incoming snippets for(x=1;x lte listLen(arguments.eventsnippets);x=x+1){ - + //.*- = the cache suffix and appendages for regex to match cacheKey = keyPrefix & replace(listGetAt(arguments.eventsnippets,x),".","\.","all") & "-.*"; - + //Check if we are purging with query string if( len(arguments.queryString) neq 0 ){ cacheKey = cacheKey & "-" & eventURLFacade.buildHash(listGetAt(arguments.queryString,x)); } regexCacheKey = regexCacheKey & cacheKey; - + //check that we aren't at the end of the list, and the | char to the regex as the OR statement if (x NEQ listLen(arguments.eventsnippets)) { regexCacheKey = regexCacheKey & "|"; } } - + // Clear All Events by Criteria clearByKeySnippet(keySnippet=regexCacheKey,regex=true); - + var cacheKey = getAssociatedCache().getEventCacheKeyPrefix(); - + // Clear All Events clearByKeySnippet(keySnippet=cacheKey,regex=false); @@ -135,12 +135,12 @@ Description : var cacheKey = getAssociatedCache().getViewCacheKeyPrefix() & arguments.viewSnippet; - + // Clear All View snippets clearByKeySnippet(keySnippet=cacheKey,regex=false); - + @@ -149,40 +149,40 @@ Description : var x = 1; var cacheKey = ""; var keyPrefix = getAssociatedCache().getViewCacheKeyPrefix(); - + // normalize snippets if( isArray(arguments.viewSnippets) ){ arguments.viewSnippets = arrayToList( arguments.viewSnippets ); } - + // Loop on the incoming snippets for(x=1;x lte listLen(arguments.viewSnippets);x=x+1){ - + //.*- = the cache suffix and appendages for regex to match cacheKey = keyPrefix & replace(listGetAt(arguments.viewSnippets,x),".","\.","all") & "-.*"; - + //Check if we are purging with query string regexCacheKey = regexCacheKey & cacheKey; - + //check that we aren't at the end of the list, and the | char to the regex as the OR statement if (x NEQ listLen(arguments.viewSnippets)) { regexCacheKey = regexCacheKey & "|"; } } - + // Clear All Events by Criteria clearByKeySnippet(keySnippet=regexCacheKey,regex=true); - + var cacheKey = getAssociatedCache().getViewCacheKeyPrefix(); - + // Clear All the views clearByKeySnippet(keySnippet=cacheKey,regex=false); - + \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/cache/util/EventURLFacade.cfc b/src/cfml/system/wirebox/system/cache/util/EventURLFacade.cfc index e0e3ced17..76046a676 100644 --- a/src/cfml/system/wirebox/system/cache/util/EventURLFacade.cfc +++ b/src/cfml/system/wirebox/system/cache/util/EventURLFacade.cfc @@ -36,7 +36,7 @@ component accessors="true"{ // Get the original incoming context hash "incomingHash" = incomingHash, // Multi-Host support - "cgihost" = cgi.http_host + "cgihost" = CGI.HTTP_HOST }; // Incorporate Routed Structs @@ -65,7 +65,7 @@ component accessors="true"{ // Get the original incoming context hash according to incoming arguments "incomingHash" = hash( virtualRC.toString() ), // Multi-Host support - "cgihost" = cgi.http_host + "cgihost" = CGI.HTTP_HOST }; // return hash from cache key struct diff --git a/src/cfml/system/wirebox/system/cache/util/ICacheStats.cfc b/src/cfml/system/wirebox/system/cache/util/ICacheStats.cfc index 8f269448c..40b103fed 100644 --- a/src/cfml/system/wirebox/system/cache/util/ICacheStats.cfc +++ b/src/cfml/system/wirebox/system/cache/util/ICacheStats.cfc @@ -1,44 +1,44 @@  - - + + - + - + - + - + - - + + - + - + - + - + - + - + - + - + diff --git a/src/cfml/system/wirebox/system/cache/util/IStats.cfc b/src/cfml/system/wirebox/system/cache/util/IStats.cfc new file mode 100644 index 000000000..eaac9cc9d --- /dev/null +++ b/src/cfml/system/wirebox/system/cache/util/IStats.cfc @@ -0,0 +1,55 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * @author Luis Majano + * + * The main interface for a CacheBox cache provider statistics object + */ +interface{ + + /** + * Get the cache's performance ratio + */ + numeric function getCachePerformanceRatio(); + + /** + * Get the associated cache's live object count + */ + numeric function getObjectCount(); + + /** + * Clear the stats + * + * @return IStats + */ + function clearStatistics(); + + /** + * Get the total cache's garbage collections + */ + numeric function getGarbageCollections(); + + /** + * Get the total cache's eviction count + */ + numeric function getEvictionCount(); + + /** + * Get the total cache's hits + */ + numeric function getHits(); + + /** + * Get the total cache's misses + */ + numeric function getMisses(); + + /** + * Get the date/time of the last reap the cache did + * + * @return date/time or empty + */ + function getLastReapDatetime(); + +} \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/core/collections/ConcurrentProcessor.cfc b/src/cfml/system/wirebox/system/core/collections/ConcurrentProcessor.cfc new file mode 100644 index 000000000..94fc63255 --- /dev/null +++ b/src/cfml/system/wirebox/system/core/collections/ConcurrentProcessor.cfc @@ -0,0 +1,197 @@ +/** + * This processor takes in a collection and can process its items in parallel according to many + * options like maxThreads and if no more threads can be created by the CFML Engine. + * + * Please note this class is a Transient do not make it a singleton or bad things can happen + */ +component accessors="true"{ + + /** + * Collection target + */ + property name="collection"; + + /** + * Max threads to leverage + */ + property name="maxThreads" type="numeric" default="20"; + + /** + * Thread priority to use + */ + property name="priority" type="string" default="normal"; + + /** + * A collection of error logs where if a thread fails, the exception data will be stored here. + */ + property name="errorLogs" type="array"; + + /** + * Init a parallel processor with the collection to process concurrently + * + * @target The target collection to proecss + * @maxThreads How many concurrent threads to use + * @priority The thread priority + */ + function init( target=[], numeric maxThreads=20, priority="normal" ){ + variables.collection = arguments.target; + variables.maxThreads = arguments.maxThreads; + variables.priority = arguments.priority; + variables.util = new wirebox.system.core.util.Util(); + variables.uuidHelper = createobject( "java", "java.util.UUID" ); + variables.consumer = function(){}; + variables.errorLogs = []; + + return this; + } + + /** + * Process each collection item via the consumer in a separate thread if it is allowed. + * Please note that this does not mutate the collection, it only porcess it + */ + ConcurrentProcessor function each( required consumer ){ + variables.consumer = arguments.consumer; + + if( isStruct( variables.collection ) ){ + return _eachStruct(); + } else if( isArray( variables.collection ) ){ + return _eachArray(); + } else { + // Queries + return _eachQuery(); + } + } + + /******************************** PRIVATE PROCESSORS ********************************/ + + /** + * Process an array + */ + private function _eachArray(){ + var threadList = []; + + for( var x = 1; x lte arrayLen( variables.collection ); x++ ){ + + // Sync mode? + if( variables.util.inThread() || threadList.len() > variables.maxThreads ){ + arguments.consumer( thisItem ); + continue; + } + + var threadName = "$box_cp_#variables.uuidHelper.randomUUID()#"; + threadList.append( threadName ); + + thread + action="run" + name="#threadName#" + priority="#variables.priority#" + threadName="#threadName#" + index=x{ + try{ + variables.consumer( + variables.collection[ attributes.index ] + ); + } + catch( any e ){ + variables.processError( e ); + } + } + } // end for loop + + // Wait for all threads to join + thread action="join" name="#threadList.toList()#"; + + return this; + } + + /** + * Process a struct + */ + private function _eachStruct(){ + var threadList = []; + + for( var thisKey in variables.collection ){ + + // Sync mode? + if( variables.util.inThread() || threadList.len() > variables.maxThreads ){ + arguments.consumer( variables.collection[ thisKey ] ); + continue; + } + + var threadName = "$box_cp_#variables.uuidHelper.randomUUID()#"; + threadList.append( threadName ); + + thread + action="run" + name="#threadName#" + priority="#variables.priority#" + threadName="#threadName#" + key=thisKey{ + try{ + variables.consumer( + variables.collection[ attributes.key ] + ); + } + catch( any e ){ + variables.processError( e ); + } + } + } // end for loop + + // Wait for all threads to join + thread action="join" name="#threadList.toList()#"; + + return this; + } + + /** + * Process a query + */ + private function _eachQuery(){ + var threadList = []; + + for( var x = 1; x lte variables.collection.recordCount; x++ ){ + + // Sync mode? + if( variables.util.inThread() || threadList.len() > variables.maxThreads ){ + arguments.consumer( thisItem ); + continue; + } + + var threadName = "$box_cp_#variables.uuidHelper.randomUUID()#"; + threadList.append( threadName ); + + thread + action="run" + name="#threadName#" + priority="#variables.priority#" + threadName="#threadName#" + index=x{ + try{ + variables.consumer( + variables.collection.getRow( attributes.index ) + ); + } + catch( any e ){ + variables.processError( e ); + } + } + } // end for loop + + // Wait for all threads to join + thread action="join" name="#threadList.toList()#"; + + return this; + } + + private function processError( required e ){ + var threadName = createObject( "java", "java.lang.Thread" ).currentThread().getName(); + var threadLog = "Error running thread (#threadName#): #arguments.e.message# #arguments.e.detail#"; + // Send them to console for debugging + writeDump( var=threadLog, output="console" ); + writeDump( var=e.stackTrace, output="console" ); + // Send them to the error logs array + variables.errorLogs.append( threadLog ); + } + +} \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/core/collections/ScopeStorage.cfc b/src/cfml/system/wirebox/system/core/collections/ScopeStorage.cfc index bb3e6a920..0fce4b333 100644 --- a/src/cfml/system/wirebox/system/core/collections/ScopeStorage.cfc +++ b/src/cfml/system/wirebox/system/core/collections/ScopeStorage.cfc @@ -5,7 +5,7 @@ * A utility Facade to help in storing data in multiple CF Storages */ component{ - + // Static list of valid scopes variables.SCOPES = "application|client|cookie|session|server|request"; @@ -54,10 +54,10 @@ component{ } else if ( structKeyExists( arguments, "defaultValue" ) ){ return arguments.defaultValue; } - + throw( type = "ScopeStorage.KeyNotFound", - message = "The key #arguments.key# does not exist in the #arguments.scope# scope." + message = "The key #arguments.key# does not exist in the #arguments.scope# scope." ); } @@ -76,12 +76,14 @@ component{ */ any function getScope( required scope ){ scopeCheck( arguments.scope ); - + switch( arguments.scope ){ - case "session" : { + case "session" : { return ( isDefined( "session" ) ? session : {} ); } - case "application" : return application; + case "application" : { + return ( isDefined( "application" ) ? application : {} ); + }; case "server" : return server; case "client" : return client; case "cookie" : return cookie; diff --git a/src/cfml/system/wirebox/system/core/conversion/ObjectMarshaller.cfc b/src/cfml/system/wirebox/system/core/conversion/ObjectMarshaller.cfc index 6154f78d6..954a80001 100644 --- a/src/cfml/system/wirebox/system/core/conversion/ObjectMarshaller.cfc +++ b/src/cfml/system/wirebox/system/core/conversion/ObjectMarshaller.cfc @@ -1,28 +1,31 @@ /** -* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp -* www.ortussolutions.com -* --- -* Allows you to serialize/deserialize objects -*/ + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * Allows you to serialize/deserialize objects + */ component accessors="true"{ /** - * Constructor - */ + * Constructor + */ function init(){ return this; } /** - * Serialize an object and optionally save it into a file. - * @target The complex object, such as a query or CFC, that will be serialized. - * @filePath The path of the file in which to save the serialized data. - */ + * Serialize an object and optionally save it into a file. + * + * @target The complex object, such as a query or CFC, that will be serialized. + * @filePath The path of the file in which to save the serialized data. + * + * @return Binary data + */ function serializeObject( required any target, string filePath ){ var binaryData = serializeWithObjectSave( arguments.target ); // Save to File? - if( structKeyExists( arguments,"filePath" ) ){ + if( !isNull( arguments.filePath ) ){ fileWrite( arguments.filePath, binaryData ); } @@ -31,17 +34,20 @@ component accessors="true"{ /** - * Deserialize an object using a binary object or a filepath - * @target The binary object to inflate - * @filePath The location of the file that has the binary object to inflate - */ + * Deserialize an object using a binary object or a filepath + * + * @target The binary object to inflate + * @filePath The location of the file that has the binary object to inflate + * + * @return Loaded Object + */ function deserializeObject( any binaryObject, string filePath ){ // Read From File? - if( structKeyExists( arguments,"filePath" ) ){ + if( !isNull( arguments.filePath ) ){ arguments.binaryObject = fileRead( arguments.filePath ); } - return deserializeWithObjectLoad(arguments.binaryObject); + return deserializeWithObjectLoad( arguments.binaryObject ); } /** @@ -53,9 +59,10 @@ component accessors="true"{ } /** - * Deserialize via ObjectLoad - * @binaryObject The binary object to inflate - */ + * Deserialize via ObjectLoad + * + * @binaryObject The binary object to inflate + */ function deserializeWithObjectLoad( any binaryObject ){ // check if string if( not isBinary( arguments.binaryObject ) ){ arguments.binaryObject = toBinary( arguments.binaryObject ); } @@ -64,9 +71,10 @@ component accessors="true"{ } /** - * Serialize via generic Java - * @target The binary object to inflate - */ + * Serialize via generic Java + * + * @target The binary object to inflate + */ function serializeGeneric( any target ){ var byteArrayOutput = createObject( "java", "java.io.ByteArrayOutputStream").init(); var objectOutput = createObject( "java", "java.io.ObjectOutputStream").init( byteArrayOutput ); @@ -79,9 +87,10 @@ component accessors="true"{ } /** - * Serialize via generic Java - * @target The binary object to inflate - */ + * Serialize via generic Java + * + * @target The binary object to inflate + */ function deserializeGeneric( any binaryObject ){ var byteArrayInput = createObject( "java", "java.io.ByteArrayInputStream").init( toBinary( arguments.binaryObject ) ); var ObjectInput = createObject( "java", "java.io.ObjectInputStream").init( byteArrayInput ); diff --git a/src/cfml/system/wirebox/system/core/conversion/XMLConverter.cfc b/src/cfml/system/wirebox/system/core/conversion/XMLConverter.cfc index 0b64e6493..21dd23d50 100644 --- a/src/cfml/system/wirebox/system/core/conversion/XMLConverter.cfc +++ b/src/cfml/system/wirebox/system/core/conversion/XMLConverter.cfc @@ -163,7 +163,7 @@ Modifications "> - #value#")> + #value#")> @@ -215,7 +215,7 @@ Modifications else{ thisValue = safeText(target[key],arguments.useCDATA); } - buffer.append("<#lcase(key)#>#thisValue#"); + buffer.append("<#key#>#thisValue#"); } // End Root @@ -233,7 +233,7 @@ Modifications var target = isNull( arguments.data ) ? "NULL" : arguments.data; var buffer = createObject("java","java.lang.StringBuilder").init(''); var md = getMetadata(target); - var rootElement = lcase( safeText( listLast( md.name, "." ) ) ); + var rootElement = safeText( listLast( md.name, "." ) ); var thisName = ""; var thisValue = ""; var x = 0; @@ -271,7 +271,7 @@ Modifications thisValue = safeText(thisValue,arguments.useCDATA); } - buffer.append("<#lcase(thisName)#>#thisValue#"); + buffer.append("<#thisName#>#thisValue#"); }//end if property has a name, else skip diff --git a/src/cfml/system/wirebox/system/core/dynamic/BeanPopulator.cfc b/src/cfml/system/wirebox/system/core/dynamic/BeanPopulator.cfc index d35d7c9b8..4054e12cf 100644 --- a/src/cfml/system/wirebox/system/core/dynamic/BeanPopulator.cfc +++ b/src/cfml/system/wirebox/system/core/dynamic/BeanPopulator.cfc @@ -17,9 +17,9 @@ component{ /** * Populate a named or instantiated instance from a Json string - * + * * @target The target to populate - * @JSONString The JSON string to populate the object with. It has to be valid JSON and also a structure with name-key value pairs. + * @JSONString The JSON string to populate the object with. It has to be valid JSON and also a structure with name-key value pairs. * @scope Use scope injection instead of setters population. Ex: scope=variables.instance. * @trustedSetter If set to true, the setter method will be called even if it does not exist in the bean * @include A list of keys to include in the population @@ -28,7 +28,7 @@ component{ * @nullEmptyInclude A list of keys to NULL when empty * @nullEmptyExclude A list of keys to NOT NULL when empty * @composeRelationships Automatically attempt to compose relationships from memento - * + * * @return The target populated with the packet */ function populateFromJson( @@ -52,7 +52,7 @@ component{ /** * Populate a named or instantiated instance from an XML Packet - * + * * @target The target to populate * @xml The XML string or packet to populate the target with * @root The XML root element to start from, else defaults to XMLRoot @@ -64,7 +64,7 @@ component{ * @nullEmptyInclude A list of keys to NULL when empty * @nullEmptyExclude A list of keys to NOT NULL when empty * @composeRelationships Automatically attempt to compose relationships from memento - * + * * @return The target populated with the packet */ function populateFromXML( @@ -110,7 +110,7 @@ component{ /** * Populate a named or instantiated instance from a Query object - * + * * @target The target to populate * @qry The query to populate the object with * @rowNumber The row number to use for population, defaults to 1 @@ -122,7 +122,7 @@ component{ * @nullEmptyInclude A list of keys to NULL when empty * @nullEmptyExclude A list of keys to NOT NULL when empty * @composeRelationships Automatically attempt to compose relationships from memento - * + * * @return The target populated with the packet */ function populateFromQuery( @@ -150,7 +150,7 @@ component{ /** * Populate a named or instantiated instance from a Query object using a column prefix - * + * * @target The target to populate * @qry The query to populate the object with * @prefix The prefix used to filter, Example: 'user_' would apply to the following columns: 'user_id' and 'user_name' but not 'address_id'. @@ -163,7 +163,7 @@ component{ * @nullEmptyInclude A list of keys to NULL when empty * @nullEmptyExclude A list of keys to NOT NULL when empty * @composeRelationships Automatically attempt to compose relationships from memento - * + * * @return The target populated with the packet */ function populateFromQueryWithPrefix( @@ -198,7 +198,7 @@ component{ /** * Populate a named or instantiated instance from a struct object using a key prefix - * + * * @target The target to populate * @memento The structure to populate the target with * @prefix The prefix used to filter, Example: 'user_' would apply to the following columns: 'user_id' and 'user_name' but not 'address_id'. @@ -210,7 +210,7 @@ component{ * @nullEmptyInclude A list of keys to NULL when empty * @nullEmptyExclude A list of keys to NOT NULL when empty * @composeRelationships Automatically attempt to compose relationships from memento - * + * * @return The target populated with the packet */ function populateFromStructWithPrefix( @@ -228,7 +228,7 @@ component{ ){ var prefixLength = len( arguments.prefix ); var newMemento = {}; - + arguments.memento .filter( function( key, value ){ return( left( key, prefixLength ) == prefix ); @@ -244,7 +244,7 @@ component{ /** * Populate a named or instantiated instance from a struct object using a key prefix - * + * * @target The target to populate * @memento The structure to populate the target with * @scope Use scope injection instead of setters population. Ex: scope=variables.instance. @@ -255,7 +255,7 @@ component{ * @nullEmptyInclude A list of keys to NULL when empty * @nullEmptyExclude A list of keys to NOT NULL when empty * @composeRelationships Automatically attempt to compose relationships from memento - * + * * @return The target populated with the packet */ function populateFromStruct( @@ -317,7 +317,7 @@ component{ pop = false; } // Ignore Empty? Check added for real Null value - if( arguments.ignoreEmpty and not IsNull(propertyValue) and isSimpleValue(arguments.memento[key]) and not len( trim( arguments.memento[key] ) ) ){ + if( arguments.ignoreEmpty and not IsNull(local.propertyValue) and isSimpleValue(arguments.memento[key]) and not len( trim( arguments.memento[key] ) ) ){ pop = false; } @@ -346,7 +346,7 @@ component{ } // Is value nullable (e.g., simple, empty string)? If so, set null... // short circuit evealuaton of IsNull added, so it won't break IsSimpleValue with Real null values. Real nulls are already set. - if( !IsNull(propertyValue) && isSimpleValue( propertyValue ) && !len( trim( propertyValue ) ) && nullValue ) { + if( !IsNull(local.propertyValue) && isSimpleValue( propertyValue ) && !len( trim( propertyValue ) ) && nullValue ) { propertyValue = JavaCast( "null", "" ); } @@ -359,7 +359,7 @@ component{ }; // If property isn't null, try to compose the relationship - if( !isNull( propertyValue ) && composeRelationships && structKeyExists( relationalMeta, key ) ) { + if( !isNull( local.propertyValue ) && composeRelationships && structKeyExists( relationalMeta, key ) ) { // get valid, known entity name list var validEntityNames = getEntityMap(); var targetEntityName = ""; @@ -418,7 +418,7 @@ component{ var structKeyColumn = relationalMeta[ key ].structKeyColumn; var keyValue = ""; // try to get struct key value from entity - if( !isNull( item ) ) { + if( !isNull( local.item ) ) { try { keyValue = invoke( item, "get#structKeyColumn#" ); } @@ -448,7 +448,7 @@ component{ } // if target entity name found } // Populate the property as a null value - if( isNull( propertyValue ) ) { + if( isNull( local.propertyValue ) ) { // Finally...set the value invoke( beanInstance, "set#key#", [ JavaCast( 'null', '' ) ] ); } @@ -464,7 +464,7 @@ component{ return beanInstance; } catch( Any e ){ - if( isNull( propertyValue ) ) { + if( isNull( local.propertyValue ) ) { arguments.keyTypeAsString = "NULL"; } else if ( isObject( propertyValue ) OR isCustomFunction( propertyValue )){ @@ -495,8 +495,8 @@ component{ .getInheritedMetaData( arguments.target, stopRecursions ) .properties .filter( function( item ){ - return ( - item.keyExists( "fieldType" ) && + return ( + item.keyExists( "fieldType" ) && item.keyExists( "name" ) && !listFindNoCase( "id,column", item.fieldtype ) ); diff --git a/src/cfml/system/wirebox/system/core/dynamic/MixerUtil.cfc b/src/cfml/system/wirebox/system/core/dynamic/MixerUtil.cfc index 443dd7487..13637bff2 100644 --- a/src/cfml/system/wirebox/system/core/dynamic/MixerUtil.cfc +++ b/src/cfml/system/wirebox/system/core/dynamic/MixerUtil.cfc @@ -14,21 +14,19 @@ Description : - instance = structnew(); - instance.mixins = StructNew(); - - // Place our methods on the mixins struct - instance.mixins[ "removeMixin" ] = variables.removeMixin; - instance.mixins[ "injectMixin" ] = variables.injectMixin; - instance.mixins[ "invokerMixin" ] = variables.invokerMixin; - instance.mixins[ "injectPropertyMixin" ] = variables.injectPropertyMixin; - instance.mixins[ "removePropertyMixin" ] = variables.removePropertyMixin; - instance.mixins[ "populatePropertyMixin" ] = variables.populatePropertyMixin; - instance.mixins[ "includeitMixin" ] = variables.includeitMixin; - instance.mixins[ "getPropertyMixin" ] = variables.getPropertyMixin; - instance.mixins[ "exposeMixin" ] = variables.exposeMixin; - instance.mixins[ "methodProxy" ] = variables.methodProxy; - instance.mixins[ "getVariablesMixin" ] = variables.getVariablesMixin; + variables.mixins = { + "removeMixin" = variables.removeMixin, + "injectMixin" = variables.injectMixin, + "invokerMixin" = variables.invokerMixin, + "injectPropertyMixin" = variables.injectPropertyMixin, + "removePropertyMixin" = variables.removePropertyMixin, + "populatePropertyMixin" = variables.populatePropertyMixin, + "includeitMixin" = variables.includeitMixin, + "getPropertyMixin" = variables.getPropertyMixin, + "exposeMixin" = variables.exposeMixin, + "methodProxy" = variables.methodProxy, + "getVariablesMixin" = variables.getVariablesMixin + }; return this; @@ -40,12 +38,7 @@ Description : - if ( NOT structKeyExists( arguments.CFC, "$mixed" ) ){ - for( var thisUDF in instance.mixins ){ - arguments.CFC[ thisUDF ] = instance.mixins[ thisUDF ]; - } - arguments.CFC.$mixed = true; - } + structAppend( arguments.cfc, variables.mixins, true ); @@ -53,10 +46,9 @@ Description : - for( var udf in instance.mixins ){ + for( var udf in variables.mixins ){ structDelete( arguments.CFC, udf ); } - structDelete( arguments.CFC, "$mixed" ); diff --git a/src/cfml/system/wirebox/system/core/events/EventPool.cfc b/src/cfml/system/wirebox/system/core/events/EventPool.cfc index 44dfbe7e9..f30dcffbd 100644 --- a/src/cfml/system/wirebox/system/core/events/EventPool.cfc +++ b/src/cfml/system/wirebox/system/core/events/EventPool.cfc @@ -22,11 +22,8 @@ component accessors="true"{ * @state The name of the pool */ function init( required state ){ - var linkedHashMap = createObject( "java", "java.util.LinkedHashMap" ).init( 5 ); - var collections = createObject( "java", "java.util.Collections" ); - // Create the event pool, start with 5 instead of 16 to save space - variables.pool = collections.synchronizedMap( linkedHashMap ); + variables.pool = createObject( "java", "java.util.LinkedHashMap" ).init( 5 ); variables.state = arguments.state; return this; @@ -120,7 +117,7 @@ component accessors="true"{ { interceptData = arguments.interceptData } ); - if( !isNull( results ) && isBoolean( results ) ){ + if( !isNull( local.results ) && isBoolean( results ) ){ return results; } diff --git a/src/cfml/system/wirebox/system/core/util/CFMappingHelper.cfc b/src/cfml/system/wirebox/system/core/util/CFMappingHelper.cfc index 008310a05..8665d30bd 100644 --- a/src/cfml/system/wirebox/system/core/util/CFMappingHelper.cfc +++ b/src/cfml/system/wirebox/system/core/util/CFMappingHelper.cfc @@ -8,7 +8,7 @@ component{ /** * Add a ColdFusion mapping - * + * * @name The name of the mapping * @path The path of the mapping */ @@ -18,4 +18,14 @@ component{ return this; } + /** + * Register a struct of CF Mappings + * + * @mappings The struct of mappings to register + */ + CFMappingHelper function addMappings( required mappings ){ + getApplicationMetadata().mappings.append( arguments.mappings ); + return this; + } + } \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/core/util/LuceeMappingHelper.cfc b/src/cfml/system/wirebox/system/core/util/LuceeMappingHelper.cfc index d878c09cf..d18ce6ce8 100644 --- a/src/cfml/system/wirebox/system/core/util/LuceeMappingHelper.cfc +++ b/src/cfml/system/wirebox/system/core/util/LuceeMappingHelper.cfc @@ -8,7 +8,7 @@ component{ /** * Add a Lucee mapping - * + * * @name The name of the mapping * @path The path of the mapping */ @@ -20,4 +20,16 @@ component{ return this; } + /** + * Add a Lucee mapping using a struct of mappings + * + * @mappings A struct of mappings to register + */ + LuceeMappingHelper function addMappings( required mappings ) { + var newMappings = getApplicationSettings().mappings.append( arguments.mappings ); + application action='update' mappings='#newMappings#'; + + return this; + } + } \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/core/util/Util.cfc b/src/cfml/system/wirebox/system/core/util/Util.cfc index c1ae5898a..a27deb46f 100644 --- a/src/cfml/system/wirebox/system/core/util/Util.cfc +++ b/src/cfml/system/wirebox/system/core/util/Util.cfc @@ -27,10 +27,10 @@ Description : return arguments.in.reduce( function( result, item, index ){ var target = {}; - if( !isNull( result ) ){ - target = result; + if( !isNull( arguments.result ) ){ + target = arguments.result; } - target[ index ] = item; + target[ arguments.index ] = arguments.item; return target; } ); @@ -142,12 +142,12 @@ Description : var value = getJavaSystem().getProperty( arguments.key ); - if ( ! isNull( value ) ) { + if ( ! isNull( local.value ) ) { return value; } value = getJavaSystem().getEnv( arguments.key ); - if ( ! isNull( value ) ) { + if ( ! isNull( local.value ) ) { return value; } @@ -168,7 +168,7 @@ Description : var value = getJavaSystem().getProperty( arguments.key ); - if ( ! isNull( value ) ) { + if ( ! isNull( local.value ) ) { return value; } @@ -189,7 +189,7 @@ Description : var value = getJavaSystem().getEnv( arguments.key ); - if ( ! isNull( value ) ) { + if ( ! isNull( local.value ) ) { return value; } @@ -355,8 +355,9 @@ Description : - - + + + var mappingHelper = ""; @@ -367,13 +368,17 @@ Description : mappingHelper = new CFMappingHelper(); } - // Add / registration - if( left( arguments.name, 1 ) != "/" ){ - arguments.name = "/#arguments.name#"; - } + if( !isNull( arguments.mappings ) ){ + mappingHelper.addMappings( arguments.mappings ); + } else { + // Add / registration + if( left( arguments.name, 1 ) != "/" ){ + arguments.name = "/#arguments.name#"; + } - // Add mapping - mappingHelper.addMapping( arguments.name, arguments.path ); + // Add mapping + mappingHelper.addMapping( arguments.name, arguments.path ); + } return this; diff --git a/src/cfml/system/wirebox/system/ioc/Builder.cfc b/src/cfml/system/wirebox/system/ioc/Builder.cfc index df1aeb3eb..88eb41855 100644 --- a/src/cfml/system/wirebox/system/ioc/Builder.cfc +++ b/src/cfml/system/wirebox/system/ioc/Builder.cfc @@ -159,10 +159,25 @@ // Invoke constructor invoke( oModel, thisMap.getConstructor(), constructorArgs ); } catch( any e ){ + + var reducedTagContext = e.tagContext.reduce( function(result, file) { + if( !result.done ) { + if( file.template.listLast( '/\' ) == 'Builder.cfc' ) { + result.done = true; + } else { + result.rows.append( '#file.template#:#file.line#' ); + } + } + return result; + }, {rows:[],done:false} ).rows.toList( chr(13)&chr(10) ); + throw( type = "Builder.BuildCFCDependencyException", - message = "Error building: #thisMap.getName()# -> #e.message#.", - detail = "DSL: #thisMap.getDSL()#, Path: #thisMap.getPath()#, Error Location: #e.tagContext[ 1 ].template#:#e.tagContext[ 1 ].line#" + message = "Error building: #thisMap.getName()# -> #e.message# + #e.detail#.", + detail = "DSL: #thisMap.getDSL()#, Path: #thisMap.getPath()#, + Error Location: + #reducedTagContext#" ); } } @@ -335,7 +350,8 @@ source = arguments.mapping.getPath(), query = "results.items", properties = "results.metadata", - timeout = "20" + timeout = "20", + userAgent = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36" ); return results; @@ -384,7 +400,13 @@ switch( DSLNamespace ){ // ColdBox Context DSL - case "coldbox" : { + case "coldbox" : case "box" : { + if( !variables.injector.isColdBoxLinked() ){ + throw( + message = "The DSLNamespace: #DSLNamespace# cannot be used as it requires a ColdBox Context", + type = "Builder.IllegalDSLException" + ); + } refLocal.dependency = variables.coldboxDSL.process( argumentCollection=arguments ); break; } @@ -455,15 +477,24 @@ // was dependency required? If so, then throw exception if( arguments.definition.required ){ + + // Build human-readable description of the mapping + var depDesc = []; + if( !isNull( arguments.definition.name ) ) { depDesc.append( "Name of '#arguments.definition.name#'" ); } + if( !isNull( arguments.definition.DSL ) ) { depDesc.append( "DSL of '#arguments.definition.DSL#'" ); } + if( !isNull( arguments.definition.REF ) ) { depDesc.append( "REF of '#arguments.definition.REF#'" ); } + + var injectMessage = "The target '#arguments.targetID#' requested a missing dependency with a #depDesc.toList( ' and ' )#"; + // Logging if( variables.log.canError() ){ - variables.log.error( "Target: #arguments.targetID# -> DSL Definition: #arguments.definition.toString()# did not produce any resulting dependency" ); + variables.log.error( injectMessage, arguments.definition ); } // Throw exception as DSL Dependency requested was not located throw( - message = "The DSL Definition #arguments.definition.toString()# did not produce any resulting dependency", - detail = "The target requesting the dependency is: '#arguments.targetID#'", + message = injectMessage, + detail = arguments.definition.toString(), type = "Builder.DSLDependencyNotFoundException" ); } @@ -706,6 +737,13 @@ } ); } + // Copy init only if the base object has it and the child doesn't. + if( !structKeyExists( arguments.target, "init" ) AND structKeyExists( baseObject, "init" ) ){ + arguments.target.injectMixin( 'init', baseObject.init ); + } + + // local reference to arguments to use in closures below + var args = arguments; baseObject.getVariablesMixin() // filter out overrides .filter( function( key, value ) { @@ -713,16 +751,18 @@ } ) .each( function( propertyName, propertyValue ){ // inject the property/method now - target.injectPropertyMixin( propertyName, propertyValue ); + if( !isNull( arguments.propertyValue ) ) { + args.target.injectPropertyMixin( propertyName, propertyValue ); + } // Do we need to do automatic generic getter/setters if( generateAccessors and baseProperties.keyExists( propertyName ) ){ - if( ! structKeyExists( target, "get#propertyName#" ) ){ - target.injectMixin( "get" & propertyName, variables.genericGetter ); + if( ! structKeyExists( args.target, "get#propertyName#" ) ){ + args.target.injectMixin( "get" & propertyName, variables.genericGetter ); } - if( ! structKeyExists( target, "set#propertyName#" ) ){ - target.injectMixin( "set" & propertyName, variables.genericSetter ); + if( ! structKeyExists( args.target, "set#propertyName#" ) ){ + args.target.injectMixin( "set" & propertyName, variables.genericSetter ); } } @@ -751,4 +791,4 @@ return variables[ propName ]; } -} +} \ No newline at end of file diff --git a/src/cfml/system/wirebox/system/ioc/Injector.cfc b/src/cfml/system/wirebox/system/ioc/Injector.cfc index 1609a98e0..206c937aa 100644 --- a/src/cfml/system/wirebox/system/ioc/Injector.cfc +++ b/src/cfml/system/wirebox/system/ioc/Injector.cfc @@ -126,7 +126,7 @@ component serializable="false" accessors="true" implements="wirebox.system.ioc.I // Scope Storages variables.scopeStorage = new wirebox.system.core.collections.ScopeStorage(); // Version - variables.version = "5.1.4+741"; + variables.version = "5.6.2+1021"; // The Configuration Binder object variables.binder = ""; // ColdBox Application Link @@ -241,7 +241,7 @@ component serializable="false" accessors="true" implements="wirebox.system.ioc.I } // process mappings for metadata and initialization. - variables.binder.processMappings(); + //variables.binder.processMappings(); // Announce To Listeners we are online iData.injector = this; @@ -388,11 +388,12 @@ component serializable="false" accessors="true" implements="wirebox.system.ioc.I **/ function buildInstance( required mapping, struct initArguments = {} ){ var thisMap = arguments.mapping; + // before construction event variables.eventManager.processState( "beforeInstanceCreation", { mapping=arguments.mapping, injector=this } - ); + ); var oModel = ""; // determine construction type @@ -449,7 +450,7 @@ component serializable="false" accessors="true" implements="wirebox.system.ioc.I // Influence the creation of the instance var result = influenceClosure( instance=oModel, injector=this ); // Allow the closure to override the entire instance if it wishes - if( !isNull( result ) ){ + if( !isNull( local.result ) ){ oModel = result; } } @@ -538,6 +539,11 @@ component serializable="false" accessors="true" implements="wirebox.system.ioc.I var scanLocations = variables.binder.getScanLocations(); var CFCName = replace( arguments.name, ".", "/", "all" ) & ".cfc"; + // If we find a :, then avoid doing lookups on the i/o system. + if( find( ":", CFCName ) ){ + return ""; + } + // Check Scan Locations In Order for( var thisScanPath in scanLocations){ // Check if located? If so, return instantiation path @@ -555,6 +561,7 @@ component serializable="false" accessors="true" implements="wirebox.system.ioc.I // debug info, NADA found! if( variables.log.canDebug() ){ variables.log.debug( "Instance: #arguments.name# was not located anywhere" ); } + return ""; } @@ -790,7 +797,13 @@ component serializable="false" accessors="true" implements="wirebox.system.ioc.I * @mapping The target mapping */ private Injector function processMixins( required targetObject, required mapping ){ - var mixin = new wirebox.system.ioc.config.Mixin().$init( arguments.mapping.getMixins() ); + // If no length, kick out + if( !arguments.mapping.getMixins().len() ){ + return this; + } + + // Process + var mixin = new wirebox.system.ioc.config.Mixin().$init( arguments.mapping.getMixins() ); // iterate and mixin baby! for( var key in mixin ){ @@ -838,7 +851,7 @@ component serializable="false" accessors="true" implements="wirebox.system.ioc.I * @DICompleteMethods The array of DI completion methods to call */ private Injector function processAfterCompleteDI(required targetObject, required DICompleteMethods) { - var DILen = arrayLen(arguments.DICompleteMethods); + var DILen = arrayLen( arguments.DICompleteMethods ); // Check for convention first if ( StructKeyExists( arguments.targetObject, "onDIComplete" ) ) { diff --git a/src/cfml/system/wirebox/system/ioc/Provider.cfc b/src/cfml/system/wirebox/system/ioc/Provider.cfc index d29e01e50..6a3028d0a 100644 --- a/src/cfml/system/wirebox/system/ioc/Provider.cfc +++ b/src/cfml/system/wirebox/system/ioc/Provider.cfc @@ -100,7 +100,7 @@ component implements="wirebox.system.ioc.IProvider" accessors="true"{ any function onMissingMethod( required missingMethodName, required missingMethodArguments ){ var results = invoke( get(), arguments.missingMethodName, arguments.missingMethodArguments ); - if ( !isNull( results ) ){ + if ( !isNull( local.results ) ){ return results; } } diff --git a/src/cfml/system/wirebox/system/ioc/config/Binder.cfc b/src/cfml/system/wirebox/system/ioc/config/Binder.cfc index b04789c73..6a7968e19 100644 --- a/src/cfml/system/wirebox/system/ioc/config/Binder.cfc +++ b/src/cfml/system/wirebox/system/ioc/config/Binder.cfc @@ -1,4 +1,4 @@ - - + @@ -294,8 +295,8 @@ Description : - - + + - + - + + + @@ -329,6 +332,15 @@ Description : + + + for( var mapping in getCurrentMapping() ) { + mapping.process(); + } + return this; + + + @@ -999,7 +1011,7 @@ Description : - + diff --git a/src/cfml/system/wirebox/system/ioc/dsl/ColdBoxDSL.cfc b/src/cfml/system/wirebox/system/ioc/dsl/ColdBoxDSL.cfc index 9614a6861..5db9b07cf 100644 --- a/src/cfml/system/wirebox/system/ioc/dsl/ColdBoxDSL.cfc +++ b/src/cfml/system/wirebox/system/ioc/dsl/ColdBoxDSL.cfc @@ -55,7 +55,7 @@ component implements="wirebox.system.ioc.dsl.IDSLBuilder" accessors="true"{ var DSLNamespace = listFirst( arguments.definition.dsl, ":" ); switch( DSLNamespace ){ - case "coldbox" : { return getColdboxDSL( argumentCollection=arguments ); } + case "coldbox" : case "box" : { return getColdboxDSL( argumentCollection=arguments ); } } // else ignore not our DSL @@ -111,6 +111,7 @@ component implements="wirebox.system.ioc.dsl.IDSLBuilder" accessors="true"{ case "router" : { return variables.injector.getInstance( "router@coldbox" ); } case "routingService" : { return variables.coldbox.getRoutingService(); } case "renderer" : { return variables.coldbox.getRenderer(); } + case "moduleconfig" : { return variables.coldbox.getSetting( "modules" ); } } // end of services break; @@ -121,7 +122,7 @@ component implements="wirebox.system.ioc.dsl.IDSLBuilder" accessors="true"{ thisLocationType = getToken( thisType, 2, ":" ); thisLocationKey = getToken( thisType, 3, ":" ); switch( thisLocationType ){ - case "setting" : { + case "setting" : case "ConfigSettings" : { // module setting? if( find( "@", thisLocationKey ) ){ moduleSettings = variables.coldbox.getSetting( "modules" ); @@ -131,8 +132,12 @@ component implements="wirebox.system.ioc.dsl.IDSLBuilder" accessors="true"{ ){ return moduleSettings[ listlast(thisLocationKey, "@" ) ].settings[ listFirst( thisLocationKey, "@" ) ]; } - else if( variables.log.canDebug() ){ - variables.log.debug( "The module requested: #listlast( thisLocationKey, "@" )# does not exist in the loaded modules. Loaded modules are #structKeyList(moduleSettings)#" ); + else { + throw( + type = "ColdBoxDSL.InvalidDSL", + message = "The DSL provided was not valid: #arguments.definition.toString()#", + detail="The module requested: #listlast( thisLocationKey, "@" )# does not exist in the loaded modules. Loaded modules are #structKeyList(moduleSettings)#" + ); } } // just get setting @@ -143,8 +148,12 @@ component implements="wirebox.system.ioc.dsl.IDSLBuilder" accessors="true"{ if( structKeyExists( moduleSettings, thisLocationKey ) ){ return moduleSettings[ thisLocationKey ].settings; } - else if( variables.log.canDebug() ){ - variables.log.debug( "The module requested: #thisLocationKey# does not exist in the loaded modules. Loaded modules are #structKeyList(moduleSettings)#" ); + else { + throw( + type = "ColdBoxDSL.InvalidDSL", + message = "The DSL provided was not valid: #arguments.definition.toString()#", + detail="The module requested: #thisLocationKey# does not exist in the loaded modules. Loaded modules are #structKeyList(moduleSettings)#" + ); } } case "moduleconfig" : { @@ -152,8 +161,12 @@ component implements="wirebox.system.ioc.dsl.IDSLBuilder" accessors="true"{ if( structKeyExists( moduleSettings, thisLocationKey ) ){ return moduleSettings[ thisLocationKey ]; } - else if( variables.log.canDebug() ){ - variables.log.debug( "The module requested: #thisLocationKey# does not exist in the loaded modules. Loaded modules are #structKeyList(moduleSettings)#" ); + else { + throw( + type = "ColdBoxDSL.InvalidDSL", + message = "The DSL provided was not valid: #arguments.definition.toString()#", + detail="The module requested: #thisLocationKey# does not exist in the loaded modules. Loaded modules are #structKeyList(moduleSettings)#" + ); } } case "fwSetting" : { return variables.coldbox.getSetting( thisLocationKey, true ); } @@ -162,12 +175,45 @@ component implements="wirebox.system.ioc.dsl.IDSLBuilder" accessors="true"{ break; } + //coldbox:{key}:{target}:{token} + case 4: { + thisLocationType = getToken(thisType,2,":"); + thisLocationKey = getToken(thisType,3,":"); + thisLocationToken = getToken(thisType,4,":"); + switch(thisLocationType){ + case "modulesettings" : { + + moduleSettings = variables.coldbox.getSetting( "modules" ); + if( structKeyExists( moduleSettings, thisLocationKey ) ){ + if( structKeyExists( moduleSettings[ thisLocationKey ].settings, thisLocationToken ) ) { + return moduleSettings[ thisLocationKey ].settings[ thisLocationToken]; + } else { + throw( + type = "ColdBoxDSL.InvalidDSL", + message="ColdBox DSL cannot find dependency using definition: #arguments.definition.toString()#", + detail="The setting requested: #thisLocationToken# does not exist in this module. Loaded settings are #structKeyList(moduleSettings[ thisLocationKey ].settings)#" + ); + } + } + else { + throw( + type = "ColdBoxDSL.InvalidDSL", + message = "The DSL provided was not valid: #arguments.definition.toString()#", + detail="The module requested: #thisLocationKey# does not exist in the loaded modules. Loaded modules are #structKeyList(moduleSettings)#" + ); + } + + } + } + break; + } } // If we get here we have a problem. throw( type = "ColdBoxDSL.InvalidDSL", - message = "The DSL provided was not valid: #arguments.definition.toString()#" + message = "The DSL provided was not valid: #arguments.definition.toString()#", + detail="Unknown DSL" ); } diff --git a/src/cfml/system/wirebox/system/ioc/readme.md b/src/cfml/system/wirebox/system/ioc/readme.md index 888303c10..69b3862d5 100644 --- a/src/cfml/system/wirebox/system/ioc/readme.md +++ b/src/cfml/system/wirebox/system/ioc/readme.md @@ -81,7 +81,7 @@ We recommend you use [CommandBox](https://www.ortussolutions.com/products/comman Unzip the download into a folder called `wirebox` in your webroot or place outside of the webroot and create a per-application mapping `/wirebox` that points to it. **Bleeding Edge Downloads** -You can always leverage our bleeding edge artifacts server to download WireBox: http://downloads.ortussolutions.com/#/ortussolutions/wirebox/ +You can always leverage our bleeding edge artifacts server to download WireBox: https://downloads.ortussolutions.com/#/ortussolutions/wirebox/ --- diff --git a/src/cfml/system/wirebox/system/ioc/scopes/CFScopes.cfc b/src/cfml/system/wirebox/system/ioc/scopes/CFScopes.cfc index 2b0b2590d..9d27510da 100644 --- a/src/cfml/system/wirebox/system/ioc/scopes/CFScopes.cfc +++ b/src/cfml/system/wirebox/system/ioc/scopes/CFScopes.cfc @@ -23,11 +23,11 @@ component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ /** * Configure the scope for operation and returns itself - * - * + * + * * @injector The linked WireBox injector * @injector.doc_generic wirebox.system.ioc.Injector - * + * * @return wirebox.system.ioc.scopes.IScope */ function init( required injector ){ @@ -39,8 +39,8 @@ component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ /** * Retrieve an object from scope or create it if not found in scope - * - * + * + * * @mapping The linked WireBox injector * @mapping.doc_generic wirebox.system.ioc.config.Mapping * @initArguments The constructor struct of arguments to passthrough to initialization @@ -54,17 +54,17 @@ component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ if( !variables.scopeStorage.exists( cacheKey, CFScope ) ){ // Lock it lock name="WireBox.#variables.injector.getInjectorID()#.#CFScope#.#cacheKey#" - type="exclusive" - timeout="30" + type="exclusive" + timeout="30" throwontimeout="true"{ - + if( !variables.scopeStorage.exists( cacheKey, CFScope ) ){ // some nice debug info. if( variables.log.canDebug() ){ - variables.log.debug( "Object: (#arguments.mapping.getName()#) not found in CFScope (#CFScope#), beggining construction." ); + variables.log.debug( "Object: (#arguments.mapping.getName()#) not found in CFScope (#CFScope#), beginning construction." ); } - + // construct the variables var target = variables.injector.buildInstance( arguments.mapping, arguments.initArguments ); @@ -88,7 +88,7 @@ component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ return target; } - + }// end lock } @@ -98,17 +98,17 @@ component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ /** * Indicates whether an object exists in scope - * + * * @mapping The linked WireBox injector * @mapping.doc_generic wirebox.system.ioc.config.Mapping - * + * * @return wirebox.system.ioc.scopes.IScope */ boolean function exists( required mapping ){ var cacheKey = "wirebox:#arguments.mapping.getName()#"; var CFScope = arguments.mapping.getScope(); - + return variables.scopeStorage.exists( cacheKey, CFScope ); } -} \ No newline at end of file +} diff --git a/src/cfml/system/wirebox/system/ioc/scopes/NoScope.cfc b/src/cfml/system/wirebox/system/ioc/scopes/NoScope.cfc index 8c8ac6cee..aca2c8f48 100644 --- a/src/cfml/system/wirebox/system/ioc/scopes/NoScope.cfc +++ b/src/cfml/system/wirebox/system/ioc/scopes/NoScope.cfc @@ -18,11 +18,11 @@ component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ /** * Configure the scope for operation and returns itself - * - * + * + * * @injector The linked WireBox injector * @injector.doc_generic wirebox.system.ioc.Injector - * + * * @return wirebox.system.ioc.scopes.IScope */ function init( required injector ){ @@ -33,8 +33,8 @@ component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ /** * Retrieve an object from scope or create it if not found in scope - * - * + * + * * @mapping The linked WireBox injector * @mapping.doc_generic wirebox.system.ioc.config.Mapping * @initArguments The constructor struct of arguments to passthrough to initialization @@ -44,7 +44,11 @@ component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ // create and return the no scope instance, no locking needed. var object = variables.injector.buildInstance( arguments.mapping, arguments.initArguments ); // wire it - variables.injector.autowire( target=object, mapping=arguments.mapping ); + variables.injector.autowire( + target = object, + mapping = arguments.mapping, + targetId = arguments.mapping.getName() + ); // send it back return object; } @@ -52,10 +56,10 @@ component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ /** * Indicates whether an object exists in scope - * + * * @mapping The linked WireBox injector * @mapping.doc_generic wirebox.system.ioc.config.Mapping - * + * * @return wirebox.system.ioc.scopes.IScope */ boolean function exists( required mapping ){ diff --git a/src/cfml/system/wirebox/system/ioc/scopes/RequestScope.cfc b/src/cfml/system/wirebox/system/ioc/scopes/RequestScope.cfc index d57d870a6..300135726 100644 --- a/src/cfml/system/wirebox/system/ioc/scopes/RequestScope.cfc +++ b/src/cfml/system/wirebox/system/ioc/scopes/RequestScope.cfc @@ -18,11 +18,11 @@ component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ /** * Configure the scope for operation and returns itself - * - * + * + * * @injector The linked WireBox injector * @injector.doc_generic wirebox.system.ioc.Injector - * + * * @return wirebox.system.ioc.scopes.IScope */ function init( required injector ){ @@ -33,8 +33,8 @@ component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ /** * Retrieve an object from scope or create it if not found in scope - * - * + * + * * @mapping The linked WireBox injector * @mapping.doc_generic wirebox.system.ioc.config.Mapping * @initArguments The constructor struct of arguments to passthrough to initialization @@ -47,16 +47,16 @@ component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ if( NOT structKeyExists( request, cacheKey ) ){ // some nice debug info. if( variables.log.canDebug() ){ - variables.log.debug( "Object: (#arguments.mapping.getName()#) not found in request scope, beggining construction." ); + variables.log.debug( "Object: (#arguments.mapping.getName()#) not found in request scope, beginning construction." ); } - + // construct it and store it, to satisfy circular dependencies var target = variables.injector.buildInstance( arguments.mapping, arguments.initArguments ); request[ cacheKey ] = target; - + // wire it variables.injector.autowire( target=target, mapping=arguments.mapping ); - + // log it if( variables.log.canDebug() ){ variables.log.debug( "Object: (#arguments.mapping.getName()#) constructed and stored in Request scope." ); @@ -71,10 +71,10 @@ component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ /** * Indicates whether an object exists in scope - * + * * @mapping The linked WireBox injector * @mapping.doc_generic wirebox.system.ioc.config.Mapping - * + * * @return wirebox.system.ioc.scopes.IScope */ boolean function exists( required mapping ){ @@ -82,4 +82,4 @@ component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ return structKeyExists( request, cacheKey ); } -} \ No newline at end of file +} diff --git a/src/cfml/system/wirebox/system/ioc/scopes/Singleton.cfc b/src/cfml/system/wirebox/system/ioc/scopes/Singleton.cfc index bc7696a1e..bf2bec02e 100644 --- a/src/cfml/system/wirebox/system/ioc/scopes/Singleton.cfc +++ b/src/cfml/system/wirebox/system/ioc/scopes/Singleton.cfc @@ -23,11 +23,11 @@ component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ /** * Configure the scope for operation and returns itself - * - * + * + * * @injector The linked WireBox injector * @injector.doc_generic wirebox.system.ioc.Injector - * + * * @return wirebox.system.ioc.scopes.IScope */ function init( required injector ){ @@ -39,8 +39,8 @@ component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ /** * Retrieve an object from scope or create it if not found in scope - * - * + * + * * @mapping The linked WireBox injector * @mapping.doc_generic wirebox.system.ioc.config.Mapping * @initArguments The constructor struct of arguments to passthrough to initialization @@ -50,20 +50,20 @@ component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ var cacheKey = lcase( arguments.mapping.getName() ); // Verify in Singleton Cache - if( NOT structKeyExists( variables.singletons, cacheKey ) ){ - + if( NOT variables.singletons.containsKey(cacheKey) ){ + // Lock it - lock name="WireBox.#variables.injector.getInjectorID()#.Singleton.#cacheKey#" - type="exclusive" - timeout="30" + lock name="WireBox.#variables.injector.getInjectorID()#.Singleton.#cacheKey#" + type="exclusive" + timeout="30" throwontimeout="true"{ - + // double lock it - if( NOT structKeyExists( variables.singletons, cacheKey) ){ + if( NOT variables.singletons.containsKey(cacheKey) ){ // some nice debug info. if( variables.log.canDebug() ){ - variables.log.debug("Object: (#cacheKey#) not found in singleton cache, beggining construction."); + variables.log.debug("Object: (#cacheKey#) not found in singleton cache, beginning construction."); } // construct the singleton object @@ -71,7 +71,7 @@ component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ // If not in wiring thread safety, store in singleton cache to satisfy circular dependencies if( NOT arguments.mapping.getThreadSafe() ){ - variables.singletons[ cacheKey ] = tmpSingleton; + variables.singletons.put(cacheKey, tmpSingleton); } // wire up dependencies on the singleton object @@ -79,7 +79,7 @@ component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ // If thread safe, then now store it in the singleton cache, as all dependencies are now safely wired if( arguments.mapping.getThreadSafe() ){ - variables.singletons[ cacheKey ] = tmpSingleton; + variables.singletons.put(cacheKey, tmpSingleton); } // log it @@ -88,26 +88,26 @@ component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ } // return it - return variables.singletons[ cacheKey ]; + return variables.singletons.get(cacheKey); } - + } // end lock } // return singleton - return variables.singletons[ cacheKey ]; + return variables.singletons.get(cacheKey); } /** * Indicates whether an object exists in scope - * + * * @mapping The linked WireBox injector * @mapping.doc_generic wirebox.system.ioc.config.Mapping - * + * * @return wirebox.system.ioc.scopes.IScope */ boolean function exists( required mapping ){ - return structKeyExists( variables.singletons, lcase( arguments.mapping.getName() ) ); + return variables.singletons.containsKey(lcase( arguments.mapping.getName() )); } /** @@ -118,4 +118,4 @@ component implements="wirebox.system.ioc.scopes.IScope" accessors="true"{ return this; } -} \ No newline at end of file +} diff --git a/src/cfml/system/wirebox/system/logging/AbstractAppender.cfc b/src/cfml/system/wirebox/system/logging/AbstractAppender.cfc index d73996985..b61d880e1 100644 --- a/src/cfml/system/wirebox/system/logging/AbstractAppender.cfc +++ b/src/cfml/system/wirebox/system/logging/AbstractAppender.cfc @@ -5,12 +5,12 @@ * This component is used as a base for creating LogBox appenders **/ component accessors="true"{ - + /** * Min logging level */ property name="levelMin" type="numeric"; - + /** * Max logging level */ @@ -46,7 +46,7 @@ component accessors="true"{ /** * Constructor - * + * * @name The unique name for this appender. * @properties A map of configuration properties for the appender" * @layout The layout class to use in this appender for custom message rendering. @@ -64,7 +64,7 @@ component accessors="true"{ variables._hash = createObject( 'java', 'java.lang.System' ).identityHashCode( this ); // Flag denoting if the appender is inited or not. This will be set by LogBox upon succesful creation and registration. variables.initialized = false; - + // Appender's Name variables.name = REreplacenocase( arguments.name, "[^0-9a-z]", "", "ALL" ); @@ -82,7 +82,7 @@ component accessors="true"{ variables.levelMax = arguments.levelMax; return this; - } + } /** * Runs after the appender has been created and registered. Implemented by Concrete appender @@ -100,7 +100,7 @@ component accessors="true"{ /** * Setter for level min - * + * * @throws AbstractAppender.InvalidLogLevelException */ AbstractAppender function setLevelMin( required levelMin ){ @@ -119,7 +119,7 @@ component accessors="true"{ /** * Setter for level max - * + * * @throws AbstractAppender.InvalidLogLevelException */ AbstractAppender function setLevelMax( required levelMax ){ @@ -145,7 +145,7 @@ component accessors="true"{ /** * convert a severity to a string - * + * * @severity The severity to convert to a string */ function severityToString( required numeric severity){ @@ -168,7 +168,7 @@ component accessors="true"{ /** * Write an entry into the appender. You must implement this method yourself. - * + * * @logEvent The logging event to log */ AbstractAppender function logMessage( required wirebox.system.logging.LogEvent logEvent ){ @@ -177,7 +177,7 @@ component accessors="true"{ /** * Checks wether a log can be made on this appender using a passed in level - * + * * @level The level to check */ boolean function canLog( required numeric level ){ @@ -186,16 +186,21 @@ component accessors="true"{ /** * Get a property from the `properties` struct - * + * * @property The property key + * @defaultValue The default value to use if not found. */ - function getProperty( required property ){ - return variables.properties[ arguments.property ]; + function getProperty( required property, defaultValue ){ + if( variables.properties.keyExists( arguments.property ) ){ + return variables.properties[ arguments.property ]; + } else if( !isNull( arguments.defaultValue ) ){ + return arguments.defaultValue; + } } /** * Set a property from the `properties` struct - * + * * @property The property key * @value The value of the property */ @@ -206,12 +211,12 @@ component accessors="true"{ /** * Validate a property from the `properties` struct - * + * * @property The property key */ boolean function propertyExists( required property ){ return structKeyExists( variables.properties, arguments.property ); - } + } /****************************************** PRIVATE *********************************************/ @@ -221,7 +226,7 @@ component accessors="true"{ private function getUtil(){ if( structKeyExists( variables, "util" ) ){ return variables.util; } variables.util = new wirebox.system.core.util.Util(); - return variables.util; + return variables.util; } /** diff --git a/src/cfml/system/wirebox/system/logging/LogBox.cfc b/src/cfml/system/wirebox/system/logging/LogBox.cfc index 1da0d56d9..6fd6bf372 100644 --- a/src/cfml/system/wirebox/system/logging/LogBox.cfc +++ b/src/cfml/system/wirebox/system/logging/LogBox.cfc @@ -66,7 +66,7 @@ component accessors="true"{ // Category Appenders variables.categoryAppenders = ""; // Version - variables.version = "5.1.4+741"; + variables.version = "5.6.2+1021"; // Link incoming ColdBox argument variables.coldbox = arguments.coldbox; @@ -293,7 +293,7 @@ component accessors="true"{ return target; } ); - return ( isNull( results ) ? structNew() : results ); + return ( isNull( local.results ) ? structNew() : results ); } /** diff --git a/src/cfml/system/wirebox/system/logging/Logger.cfc b/src/cfml/system/wirebox/system/logging/Logger.cfc index 8c43b3ee6..fe796674a 100644 --- a/src/cfml/system/wirebox/system/logging/Logger.cfc +++ b/src/cfml/system/wirebox/system/logging/Logger.cfc @@ -20,7 +20,7 @@ component accessors="true"{ * Appenders linked to this logger */ property name="appenders" type="struct"; - + /** * Level Min */ @@ -36,13 +36,13 @@ component accessors="true"{ /** * Constructor - * + * * @category The category name to use this logger with * @levelMin The default log level for this appender, by default it is 0. Optional. ex: LogBox.logLevels.WARN * @levelMax The default log level for this appender, by default it is 0. Optional. ex: LogBox.logLevels.WARN * @appenders A struct of already created appenders for this category, or blank to use the root logger. */ - function init( + function init( required category, numeric levelMin=0, numeric levelMax=4, @@ -60,7 +60,7 @@ component accessors="true"{ // Utilities variables._hash = createObject( 'java','java.lang.System').identityHashCode( this ); variables.util = new wirebox.system.core.util.Util(); - + // Local Locking variables.lockName = variables._hash & "LoggerOperation"; @@ -85,9 +85,9 @@ component accessors="true"{ /** * Get an appender reference, if the appender does not exist it will throw an exception - * + * * @name The appender's name - * + * * @throws Logger.AppenderNotFound */ function getAppender( required name ){ @@ -104,7 +104,7 @@ component accessors="true"{ /** * Check if an appender exists - * + * * @name The name of the appender */ boolean function appenderExists( required name ){ @@ -113,9 +113,9 @@ component accessors="true"{ /** * Add a new appender to the list of appenders for this logger. If the appender already exists, then it will not be added. - * + * * @newAppender An appender object reference - * + * * @throws Logger.InvalidAppenderNameException */ Logger function addAppender( required newAppender ){ @@ -126,7 +126,7 @@ component accessors="true"{ type = "Logger.InvalidAppenderNameException" ); } - + // Get name var name = arguments.newAppender.getName(); if( ! appenderExists( name ) ){ @@ -185,9 +185,9 @@ component accessors="true"{ /** * Set the min level - * + * * @levelMin the level to set - * + * * @throws Logger.InvalidLogLevelException */ Logger function setLevelMin( required levelMin ){ @@ -197,7 +197,7 @@ component accessors="true"{ } // Verify level if( this.logLevels.isLevelValid( arguments.levelMin ) AND - arguments.levelMin lte getLevelMax() + arguments.levelMin lte getLevelMax() ){ variables.levelMin = arguments.levelMin; } else { @@ -213,9 +213,9 @@ component accessors="true"{ /** * Set the max level - * + * * @levelMax the level to set - * + * * @throws Logger.InvalidLogLevelException */ Logger function setLevelMax( required levelMax ){ @@ -225,7 +225,7 @@ component accessors="true"{ } // Verify level if( this.logLevels.isLevelValid( arguments.levelMax ) AND - arguments.levelMax gte getLevelMin() + arguments.levelMax gte getLevelMin() ){ variables.levelMax = arguments.levelMax; } else { @@ -241,10 +241,10 @@ component accessors="true"{ /** * Log a debug message - * + * * @message The message to log * @extraInfo Extra information to send to appenders - * + * * @return Logger */ function debug( required message, extraInfo="" ){ @@ -254,10 +254,10 @@ component accessors="true"{ /** * Log a info message - * + * * @message The message to log * @extraInfo Extra information to send to appenders - * + * * @return Logger */ function info( required message, extraInfo="" ){ @@ -267,10 +267,10 @@ component accessors="true"{ /** * Log a warn message - * + * * @message The message to log * @extraInfo Extra information to send to appenders - * + * * @return Logger */ function warn( required message, extraInfo="" ){ @@ -280,10 +280,10 @@ component accessors="true"{ /** * Log an error message - * + * * @message The message to log * @extraInfo Extra information to send to appenders - * + * * @return Logger */ function error( required message, extraInfo="" ){ @@ -293,10 +293,10 @@ component accessors="true"{ /** * Log a fatal message - * + * * @message The message to log * @extraInfo Extra information to send to appenders - * + * * @return Logger */ function fatal( required message, extraInfo="" ){ @@ -306,7 +306,7 @@ component accessors="true"{ /** * Write an entry into the loggers registered with this LogBox variables - * + * * @message The message to log * @severity The severity level to log, if invalid, it will default to **Info** * @extraInfo Extra information to send to appenders @@ -321,13 +321,13 @@ component accessors="true"{ // If message empty, just exit arguments.message = trim( arguments.message ); - if( NOT len( arguments.message ) ){ - return this; + if( NOT len( arguments.message ) ){ + return this; } //Is Logging Enabled? - if( getLevelMin() eq this.logLevels.OFF ){ - return this; + if( getLevelMin() eq this.logLevels.OFF ){ + return this; } // Can we log on target @@ -340,30 +340,32 @@ component accessors="true"{ if( NOT hasAppenders() ){ target = getRootLogger(); } - // Get appenders - var appenders = target.getAppenders(); - // Delegate Calls to appenders - for( var key in appenders ){ - // Get Appender - var thisAppender = appenders[ key ]; - // Log the message in the appender if the appender allows it - if( thisAppender.canLog( arguments.severity ) ){ - // check to see if the async property was passed during definition - if( thisAppender.propertyExists( 'async' ) && thisAppender.getProperty( 'async' ) ) { - // prepare threading variables. - var threadName = "logMessage_#replace( createUUID(), "-", "", "all" )#"; - // Are we in a thread already? - if( variables.util.inThread() ) { - thisAppender.logMessage( logEvent ); - } else { - // Thread this puppy - thread action="run" name="#threadName#" logEvent="#logEvent#" thisAppender="#thisAppender#"{ - attributes.thisAppender.logMessage( attributes.logEvent ); - } - } - } else { - thisAppender.logMessage( logEvent ); + + + // Process all appenders + var targetAppenders = target.getAppenders() + // Only go through appenders that can log + .filter( function( key, thisAppender ){ + return thisAppender.canLog( severity ); + } ); + + for( var key in targetAppenders ){ + var thisAppender = targetAppenders[ key ]; + // check to see if the async property was passed during definition and not in a thread already + if( + thisAppender.getProperty( 'async', false ) && + !variables.util.inThread() + ){ + // Thread this puppy + thread action = "run" + name = "logMessage_#replace( createUUID(), "-", "", "all" )#" + logEvent = "#logEvent#" + thisAppender = "#thisAppender#" + { + attributes.thisAppender.logMessage( attributes.logEvent ); } + } else { + thisAppender.logMessage( logEvent ); } } } @@ -373,7 +375,7 @@ component accessors="true"{ /** * Checks wether a log can be made on this Logger using a passed in level - * + * * @level The numeric or string level representation */ boolean function canLog( required level ){ diff --git a/src/cfml/system/wirebox/system/logging/appenders/ConsoleAppender.cfc b/src/cfml/system/wirebox/system/logging/appenders/ConsoleAppender.cfc index 4408dcf56..09401121e 100644 --- a/src/cfml/system/wirebox/system/logging/appenders/ConsoleAppender.cfc +++ b/src/cfml/system/wirebox/system/logging/appenders/ConsoleAppender.cfc @@ -4,60 +4,219 @@ * --- * Console Appender */ -component extends="wirebox.system.logging.AbstractAppender"{ +component accessors="true" extends="wirebox.system.logging.AbstractAppender"{ /** - * Constructor - * - * @name The unique name for this appender. - * @properties A map of configuration properties for the appender. - * @layout The layout class to use in this appender for custom message rendering. - * @levelMin The default log level for this appender, by default it is 0. Optional. ex: LogBox.logLevels.WARN - * @levelMax The default log level for this appender, by default it is 4. Optional. ex: LogBox.logLevels.WARN - */ - public ConsoleAppender function init( - required string name, - struct properties = {}, - string layout = "", - string levelMin = 0, - string levelMax = 4 + * The default lock name + */ + property name="lockName"; + + /** + * The default lock timeout + */ + property name="lockTimeout" default="25" type="numeric"; + + /** + * Log Listener Queue + */ + property name="logListener" type="struct"; + + /** + * Constructor + * + * @name The unique name for this appender. + * @properties A map of configuration properties for the appender" + * @layout The layout class to use in this appender for custom message rendering. + * @levelMin The default log level for this appender, by default it is 0. Optional. ex: LogBox.logLevels.WARN + * @levelMax The default log level for this appender, by default it is 5. Optional. ex: LogBox.logLevels.WARN + */ + function init( + required name, + struct properties={}, + layout="", + levelMin=0, + levelMax=4 ){ super.init( argumentCollection=arguments ); + + // Output Streams variables.out = createObject( "java", "java.lang.System" ).out; variables.error = createObject( "java", "java.lang.System" ).err; - return this; - } - /** - * Write entry into the appender - * - * @logEvent The logging event. - */ - function logMessage( required any logEvent ) { - var entry = ""; + // lock information + variables.lockName = getHash() & getName() & "logOperation"; + variables.lockTimeout = 25; + + // Activate Log Listener Queue + variables.logListener = { + active = false, + queue = [] + }; + + // Declare locking construct + variables.lock = function( type="exclusive", body ){ + lock name="#getHash() & getName()#-logListener" + type=arguments.type + timeout="#variables.lockTimeout#" + throwOnTimeout=true{ + + return arguments.body(); + + } + }; + + return this; + } + + /** + * Write an entry into the appender. + * + * @logEvent The logging event to log + */ + function logMessage( required logEvent ){ + var loge = arguments.logEvent; + var timestamp = loge.getTimestamp(); + var message = loge.getMessage(); + var entry = ""; + + // Message Layout if( hasCustomLayout() ){ - entry = getCustomLayout().format( logEvent ); + entry = getCustomLayout().format( loge ); } else { - entry = severityToString( logEvent.getseverity() ) & " " & - logEvent.getCategory() & " " & - logEvent.getmessage() & " ExtraInfo: " & - logEvent.getextraInfoAsString(); + // Cleanup main message + if( len( loge.getExtraInfoAsString() ) ){ + message &= " ExtraInfo: " & loge.getExtraInfoAsString(); + } + + // Entry string + entry = '#dateformat( timestamp, "yyyy-mm-dd" )# #timeformat( timestamp, "HH:MM:SS" )# #loge.getCategory()# #message#'; } + + // Log it switch( logEvent.getSeverity() ){ // Fatal + Error go to error stream case "0" : case "1" : { // log message - variables.error.println( entry ); + append( message=entry, isError=true ); break; } // Warning and above go to info stream default : { // log message - variables.out.println( entry ); + append( message=entry, isError=false ); break; } } + return this; + } + + /** + * Start the log listener so we can queue up the logging to alleviate for disk operations + */ + function startLogListener(){ + + // Verify if listener has started. + var isActive = variables.lock( "readonly", function(){ + return variables.logListener.active; + } ); + + if( isActive ){ + //out( "Listener already active exiting startup..." ); + return; + } else { + //out( "Listener needs to startup" ); + } + + thread action="run" name="#variables.lockName#-#hash( createUUID() )#"{ + // Activate listener + var isActivating = variables.lock( body=function(){ + if( !variables.logListener.active ){ + //out( "listener #getHash()# min: #getLevelMin()# max: #getLevelMax()# marked as active" ); + variables.logListener.active = true; + return true; + } else { + //out( "listener was just marked as active, just existing lock" ); + return false; + } + } ); + + if( !isActivating ){ return; } + + var lastRun = getTickCount(); + var start = lastRun; + var maxIdle = 15000; // 15 seconds is how long the threads can live for. + var sleepInterval = 25; + var count = 0; + var hasMessages = false; + + try{ + //out( "Starting #getName()# thread", true ); + + // Execute only if there are messages in the queue or the internal has been crossed + while( + variables.logListener.queue.len() || lastRun + maxIdle > getTickCount() + ){ + + //out( "len: #variables.logListener.queue.len()# last run: #lastRun# idle: #maxIdle#" ); + + if( variables.logListener.queue.len() ){ + // pop and dequeue + var thisMessage = variables.logListener.queue[ 1 ]; + variables.logListener.queue.deleteAt( 1 ); + + //out( "writing #thisMessage.toString()#" ); + + if( thisMessage.isError ){ + variables.error.println( thisMessage.message ); + } else { + variables.out.println( thisMessage.message ); + } + + // Mark the last run + lastRun = getTickCount(); + } + + //out( "Sleeping: lastRun #lastRun + maxIdle#" ); + + sleep( sleepInterval ); // take a nap + } + + } catch( Any e ){ + $log( "ERROR", "Error processing log listener: #e.message# #e.detail# #e.stacktrace#" ); + //out( "Error with listener thread for #getName()#" & e.message & e.detail ); + } finally { + //out( "Stopping listener thread for #getName()#, we have done our job" ); + + // Stop log listener + variables.lock( body=function(){ + variables.logListener.active = false; + } ); + } + + } // end threading + } + + /************************************ PRIVATE ************************************/ + + /** + * Append a message to the log file + * + * @message The target message + * @isError Does this go to the error stream? + */ + private ConsoleAppender function append( required message, required isError ){ + // If we are not in a thread, then start the log listener, else queue it + if( !getUtil().inThread() ){ + // Ensure log listener + startLogListener(); + } + + // queue message up + variables.logListener.queue.append( { + message = arguments.message, + isError = arguments.isError + } ); return this; } diff --git a/src/cfml/system/wirebox/system/logging/appenders/FileAppender.cfc b/src/cfml/system/wirebox/system/logging/appenders/FileAppender.cfc index 88a144477..7975f77c4 100644 --- a/src/cfml/system/wirebox/system/logging/appenders/FileAppender.cfc +++ b/src/cfml/system/wirebox/system/logging/appenders/FileAppender.cfc @@ -119,7 +119,6 @@ component accessors="true" extends="wirebox.system.logging.AbstractAppender"{ var message = loge.getMessage(); var entry = ""; - // Message Layout if( hasCustomLayout() ){ entry = getCustomLayout().format( loge ); @@ -210,11 +209,6 @@ component accessors="true" extends="wirebox.system.logging.AbstractAppender"{ //out( "Listener needs to startup" ); } - // Check if we are in a thread already, if so, just skip - if( getUtil().inThread() ){ - return; - } - thread action="run" name="#variables.lockName#-#hash( createUUID() )#"{ // Activate listener var isActivating = variables.lock( body=function(){ @@ -236,10 +230,10 @@ component accessors="true" extends="wirebox.system.logging.AbstractAppender"{ var flushInterval = 1000; // 1 second var sleepInterval = 50; var count = 0; - + // Ensure Log File initLogLocation(); - + var oFile = fileOpen( variables.logFullPath, "append", this.getProperty( "fileEncoding" ) ); var hasMessages = false; @@ -312,8 +306,11 @@ component accessors="true" extends="wirebox.system.logging.AbstractAppender"{ * @message The target message */ private FileAppender function append( required message ){ - // Ensure log listener - startLogListener(); + // If we are not in a thread, then start the log listener, else queue it + if( !getUtil().inThread() ){ + // Ensure log listener + startLogListener(); + } // queue message up arrayAppend( variables.logListener.queue, arguments.message ); diff --git a/src/cfml/system/wirebox/system/logging/config/LogBoxConfig.cfc b/src/cfml/system/wirebox/system/logging/config/LogBoxConfig.cfc index bd8d6ef12..32c0de8da 100644 --- a/src/cfml/system/wirebox/system/logging/config/LogBoxConfig.cfc +++ b/src/cfml/system/wirebox/system/logging/config/LogBoxConfig.cfc @@ -259,36 +259,41 @@ component accessors="true"{ struct function getRoot(){ return instance.rootLogger; } - + /** * Add a new category configuration with appender(s). Appenders MUST be defined first, else this method will throw an exception - * + * * @name A unique name for the appender to register. Only unique names can be registered per instance * @levelMin The default log level for the root logger, by default it is 0 (FATAL). Optional. ex: config.logLevels.WARN * @levelMax The default log level for the root logger, by default it is 4 (DEBUG). Optional. ex: config.logLevels.WARN * @appenders A list of appender names to configure this category with. By default it uses all the registered appenders */ - LogBoxConfig function category( - required name, + LogBoxConfig function category( + required name, levelMin=0, levelMax=4, appenders="*" ){ // Convert Levels convertLevels( arguments ); - + // Check levels - levelChecks( arguments.levelMin, arguments.levelMax ); - + levelChecks( arguments.levelMin, arguments.levelMax ); + + // Check * all appenders + if( appenders eq "*" ){ + appenders = structKeyList( getAllAppenders() ); + } + // Add category registration instance.categories[ arguments.name ] = arguments; - + return this; } - + /** * Get a specified category definition - * + * * @name The category name */ struct function getCategory( required name ){ diff --git a/src/cfml/system/wirebox/system/logging/readme.md b/src/cfml/system/wirebox/system/logging/readme.md index 8b812849c..4ef259133 100644 --- a/src/cfml/system/wirebox/system/logging/readme.md +++ b/src/cfml/system/wirebox/system/logging/readme.md @@ -92,7 +92,7 @@ We recommend you use [CommandBox](http://www.ortussolutions.com/products/command Unzip the download into a folder called `logbox` in your webroot or place outside of the webroot and create a per-application mapping `/logbox` that points to it. **Bleeding Edge Downloads** -You can always leverage our bleeding edge artifacts server to download LogBox: http://downloads.ortussolutions.com/#/ortussolutions/logbox/ +You can always leverage our bleeding edge artifacts server to download LogBox: https://downloads.ortussolutions.com/#/ortussolutions/logbox/ --- From a649f87ae5d6b61aed718a46147abf172671fab9 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 3 Feb 2020 21:28:57 -0800 Subject: [PATCH 080/102] Remove unavailable message param (#216) --- src/cfml/system/util/CommandDSL.cfc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfml/system/util/CommandDSL.cfc b/src/cfml/system/util/CommandDSL.cfc index 22225f21a..0647da401 100644 --- a/src/cfml/system/util/CommandDSL.cfc +++ b/src/cfml/system/util/CommandDSL.cfc @@ -240,7 +240,7 @@ component accessors=true { if( shell.getExitCode() != 0 ) { if( job.isActive() ) { - job.errorRemaining( message ); + job.errorRemaining(); // Distance ourselves from whatever other output the command may have given so far. shell.printString( chr( 10 ) ); } From 251643fc6ae0705a624a1c8b1bddf144bc6309c8 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 4 Feb 2020 11:05:15 -0800 Subject: [PATCH 081/102] COMMANDBOX-1096 --- .../system-commands/commands/unique.cfc | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/cfml/system/modules_app/system-commands/commands/unique.cfc diff --git a/src/cfml/system/modules_app/system-commands/commands/unique.cfc b/src/cfml/system/modules_app/system-commands/commands/unique.cfc new file mode 100644 index 000000000..a5cc9a68c --- /dev/null +++ b/src/cfml/system/modules_app/system-commands/commands/unique.cfc @@ -0,0 +1,36 @@ +/** + * Search through string input and filter duplicate lines. Only unique results will be returned. + * . + * {code:bash} + * cat names.txt | unique + * {code} + * . + * You can also get results with the occurance count preceeding each item + * . + * {code:bash} + * cat names.txt | unique --count + * {code} + * + **/ +component { + + /** + * @input The piped input to be checked. + * @count Precede each line with the number of times that item appeared + **/ + function run( input='', count=false ) { + // Turn output into an array, breaking on carriage returns + var content = listToArray( arguments.input, chr(13)&chr(10) ); + var uniqueMap = {}; + + // Loop over content + for( var line in content ) { + uniqueMap[ line ] = uniqueMap[ line ] ?: 0 + uniqueMap[ line ]++; + } + uniqueMap.each( (k,v)=>{ + print.line( (count ? v & ' ' : '' ) & k ); + } ); + } + +} From ca24770c2a1998254a0a1fb964afd82dcd1138b6 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 4 Feb 2020 11:06:46 -0800 Subject: [PATCH 082/102] COMMANDBOX-1097 --- .../system-commands/commands/sort.cfc | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/cfml/system/modules_app/system-commands/commands/sort.cfc diff --git a/src/cfml/system/modules_app/system-commands/commands/sort.cfc b/src/cfml/system/modules_app/system-commands/commands/sort.cfc new file mode 100644 index 000000000..935f80009 --- /dev/null +++ b/src/cfml/system/modules_app/system-commands/commands/sort.cfc @@ -0,0 +1,39 @@ +/** + * Sort a list of input lines. You can control direction and type of sort. + * . + * {code:bash} + * cat names.txt | sort + * {code} + * . + * You can do a case sensitive or numeric sort + * . + * {code:bash} + * cat names.txt | sort type=text + * cat names.txt | sort type=numeric + * {code} + * . + * You can also change the direction of the sort + * . + * {code:bash} + * cat names.txt | sort direction=desc + * {code} + * + **/ +component { + + /** + * @input The piped input to be checked. + * @type Sort by "text" (case sensitive), "textnocase" (case insenstive), or "numeric" + * @direction Sort "asc" (ascending), or "desc" (descending) + * @type.options text,textnocase,numeric + * @direction.options asc,desc + **/ + function run( input='', type="textnocase", direction="asc" ) { + print.text( + listToArray( arguments.input, chr(13)&chr(10) ) + .sort( type, direction ) + .toList( CR ) + ); + } + +} From d8d07c9896678ce95bcb150edc4df1f438ae9052 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 4 Feb 2020 22:12:11 -0800 Subject: [PATCH 083/102] COMMANDBOX-1092 --- src/cfml/system/services/CommandService.cfc | 14 +++----- src/cfml/system/services/PackageService.cfc | 40 ++++++++++++++------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/cfml/system/services/CommandService.cfc b/src/cfml/system/services/CommandService.cfc index f9d10abf2..f5e5e9c18 100644 --- a/src/cfml/system/services/CommandService.cfc +++ b/src/cfml/system/services/CommandService.cfc @@ -289,7 +289,7 @@ component accessors="true" singleton { // correctly remove this call from the stack in the finally block. try { var FRTrans = FRTransService.startTransaction( commandInfo.commandString.listChangeDelims( ' ', '.' ), commandInfo.originalLine ); - + // If we're using postitional params, convert them to named if( arrayLen( parameterInfo.positionalParameters ) ){ parameterInfo.namedParameters = convertToNamedParameters( parameterInfo.positionalParameters, commandParams ); @@ -339,16 +339,10 @@ component accessors="true" singleton { parameterInfo.namedParameters.interactive=false; } - - // Run the command + // Run the command var result = commandInfo.commandReference.CFC.run( argumentCollection = parameterInfo.namedParameters ); lastCommandErrored = commandInfo.commandReference.CFC.hasError(); - - // If the previous command failed - var finalExitCode = commandInfo.commandReference.CFC.getExitCode(); - if( finalExitCode != 0 ) { - throw( message='Command returned failing exit code (#finalExitCode#)', detail='Failing Command: #commandInfo.originalLine#', type="commandException", errorCode=finalExitCode ); - } + } catch( any e ){ FRTransService.errorTransaction( FRTrans, e.getPageException() ); lastCommandErrored = true; @@ -390,7 +384,7 @@ component accessors="true" singleton { instance.callStack.deleteAt( 1 ); // Set command exit code into the shell shell.setExitCode( commandInfo.commandReference.CFC.getExitCode() ); - + FRTransService.endTransaction( FRTrans ); } diff --git a/src/cfml/system/services/PackageService.cfc b/src/cfml/system/services/PackageService.cfc index 2559e9d77..dce1a37f1 100644 --- a/src/cfml/system/services/PackageService.cfc +++ b/src/cfml/system/services/PackageService.cfc @@ -29,7 +29,7 @@ component accessors="true" singleton { property name='wirebox' inject='wirebox'; property name="tempDir" inject="tempDir@constants"; property name="serverService" inject="serverService"; - + /** * Constructor */ @@ -122,7 +122,7 @@ component accessors="true" singleton { var packageName = endpointData.endpoint.getDefaultName( endpointData.package ); var version = '1.0.0'; } - + // If the dependency struct in box.json has a name, use it. This is mostly for // HTTP, Jar, and Lex endpoints to be able to override their package name. if( defaultName.len() && packageName != defaultName ) { @@ -347,12 +347,12 @@ component accessors="true" singleton { } else if( packageType == 'jars' ) { installDirectory = arguments.packagePathRequestingInstallation & '/lib'; } else if( packageType == 'lucee-extensions' ) { - // This is making several assumption, but if the directory of the installation is a Lucee server, then + // This is making several assumption, but if the directory of the installation is a Lucee server, then // assume the user wants this lex to be dropped in their server context's deploy folder. To override this // behavior, specify a custom install directory in your box.json or in the "install" params. var serverDetails = serverService.resolveServerDetails( { directory = arguments.packagePathRequestingInstallation } ); var serverInfo = serverDetails.serverInfo; - + if( !serverDetails.serverIsNew && serverInfo.engineName == 'lucee' && len( serverInfo.serverConfigDir ) ) { var serverDeployFolder = serverInfo.serverConfigDir & '/lucee-server/deploy/'; // Handle paths relative to the server home dir @@ -364,10 +364,10 @@ component accessors="true" singleton { job.addWarnLog( "Defaulting lex Install to [#serverDeployFolder#]" ); installDirectory = serverDeployFolder; artifactDescriptor.createPackageDirectory = false; - ignorePatterns.append( '/box.json' ); + ignorePatterns.append( '/box.json' ); } } - + } } @@ -396,7 +396,7 @@ component accessors="true" singleton { } // Some packages may just want to be dumped in their destination without being contained in a subfolder - // If the box.json had an explicit override for the install directory, then we're just going to use it directly + // If the box.json had an explicit override for the install directory, then we're just going to use it directly if( artifactDescriptor.createPackageDirectory || structKeyExists( containerBoxJSON.installPaths, packageName ) ) { installDirectory &= '/#packageDirectory#'; // If we're dumping in the root and the install dir is already another package then ignore box.json or it will overwrite the existing one @@ -796,8 +796,8 @@ component accessors="true" singleton { // Get reference to appropriate dependency struct if( arguments.dev ) { boxJSONRaw[ 'devDependencies' ] = boxJSONRaw.devDependencies ?: {}; - boxJSON[ 'devDependencies' ] = boxJSON.devDependencies ?: {}; - var dependenciesRaw = boxJSONRaw.devDependencies; + boxJSON[ 'devDependencies' ] = boxJSON.devDependencies ?: {}; + var dependenciesRaw = boxJSONRaw.devDependencies; var dependencies = boxJSON.devDependencies; } else { boxJSONRaw[ 'dependencies' ] = boxJSONRaw.dependencies ?: {}; @@ -864,12 +864,12 @@ component accessors="true" singleton { // Prevent unneccessary updates to the JSON file. if( !installPaths.keyExists( arguments.packageName ) - // Resolve the install path in box.json. If it's relative like ../lib but it's still equivalent to the actual install dir, then leave it alone. The user probably wants to keep it relative! + // Resolve the install path in box.json. If it's relative like ../lib but it's still equivalent to the actual install dir, then leave it alone. The user probably wants to keep it relative! || fileSystemUtil.normalizeSlashes( fileSystemUtil.resolvePath( installPaths[ arguments.packageName ], arguments.currentWorkingDirectory ) ) != arguments.installDirectory ) { - + installPaths[ arguments.packageName ] = arguments.installDirectory; updated = true; - + } } @@ -1226,7 +1226,7 @@ component accessors="true" singleton { if( systemSettings.getAllEnvironments().len() > 1 ) { systemSettings.setDeepSystemSettings( interceptData ); } - + // Run preXXX package script runScript( 'pre#arguments.scriptName#', arguments.directory, true ); @@ -1235,12 +1235,26 @@ component accessors="true" singleton { consoleLogger.warn( 'Running package script [#arguments.scriptName#].' ); consoleLogger.debug( '> ' & thisScript ); + // Normally the shell retains the previous exit code, but in this case + // it's important for us to know if the scripts return a failing exit code wihtout throwing an exception + shell.setExitCode( 0 ); + // ... then run the script! (in the context of the package's working directory) var previousCWD = shell.pwd(); shell.cd( arguments.directory ); shell.callCommand( thisScript ); shell.cd( previousCWD ); + // If the script ran "exit" + if( !shell.getKeepRunning() ) { + // Just kidding, the shell can stay.... + shell.setKeepRunning( true ); + } + + if( shell.getExitCode() != 0 ) { + throw( message='Package script returned failing exit code (#shell.getExitCode()#)', detail='Failing script: #arguments.scriptName#', type="commandException", errorCode=shell.getExitCode() ); + } + // Run postXXX package script runScript( 'post#arguments.scriptName#', arguments.directory, true ); From d750bf8a0ad3c31b7172e0ade6e88c98fa89b938 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 4 Feb 2020 22:16:19 -0800 Subject: [PATCH 084/102] COMMANDBOX-1098 --- .../system-commands/commands/forEach.cfc | 32 +++++++++---------- .../system-commands/commands/grep.cfc | 6 ++-- .../system-commands/commands/sed.cfc | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/cfml/system/modules_app/system-commands/commands/forEach.cfc b/src/cfml/system/modules_app/system-commands/commands/forEach.cfc index 3ee29033a..ec9386af1 100644 --- a/src/cfml/system/modules_app/system-commands/commands/forEach.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/forEach.cfc @@ -1,7 +1,7 @@ /** * Excecute a command against every item in an incoming list. The list can be passed directly - * or piped into this command. The default delimiter is a new line so this works great piping - * the output of file listings direclty in, which have a file name per line. + * or piped into this command. The default delimiter is a new line so this works great piping + * the output of file listings direclty in, which have a file name per line. * . * This powerful construct allows you to perform basic loops from the CLI over arbitrary input. * Most of the examples show file listings, but any input can be used that you want to iterate over. @@ -25,7 +25,7 @@ * {code} * . * If you want a more complex command, you can choose exactly where you wish to use the incoming item - * by referencing the default system setting expansion of ${item}. Remember to escape the expansion in + * by referencing the default system setting expansion of ${item}. Remember to escape the expansion in * your command so it's resolution is deferred until the forEach runs it internally. * Here we echo each file name followed by the contents of the file. * {code} @@ -53,19 +53,19 @@ component { string input='', string command='echo', string itemName='item', - string delimiter=CR, + string delimiter=chr(13)&chr(10), string valueName='value', boolean continueOnError=false, boolean debug=false ) { var wasJSON = false; var inputJSON = ''; - + arguments.input = print.unANSI( arguments.input ); - + if( isJSON( arguments.input ) ) { var inputJSON = deserializeJSON( arguments.input ); - + if( isArray( inputJSON ) || isStruct( inputJSON ) ) { var content = inputJSON; wasJSON = true; @@ -75,23 +75,23 @@ component { // Turn output into an array, breaking on delimiter var content = listToArray( arguments.input, delimiter ); } - + // Loop over content for( var line in content ) { var theCommand = this.command( arguments.command ); - + if( !isSimpleValue( line ) ) { line = serializeJSON( line ); } - + // If it doesn't look like they are using the placeholder, then set the item as the next param if( !arguments.command.findNoCase( '${#itemName#' ) && !arguments.command.findNoCase( '${#valueName#' ) ) { theCommand.params( line ); } - + // Set this as a localized environment variable so the command can access it. systemSettings.setSystemSetting( itemName, line ); - + // If foreach was passed a struct, set the value as well if( isStruct( inputJSON ) ) { var thisValue = content[line ]; @@ -100,24 +100,24 @@ component { } systemSettings.setSystemSetting( valueName, thisValue ); } - + try { theCommand.run( echo=debug ); } catch( any var e ) { if( continueOnError ) { - + print .redLine( e.message ) .redLine( e.detail ?: '' ) .toConsole(); - + } else { rethrow; } } finally { checkinterrupted(); } - + } } diff --git a/src/cfml/system/modules_app/system-commands/commands/grep.cfc b/src/cfml/system/modules_app/system-commands/commands/grep.cfc index 5a39ce7be..3c2bd9830 100644 --- a/src/cfml/system/modules_app/system-commands/commands/grep.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/grep.cfc @@ -26,7 +26,7 @@ component excludeFromHelp=true { **/ function run( input='', expression='', boolean count=false ) { // Turn output into an array, breaking on carriage returns - var content = listToArray( arguments.input, CR ); + var content = listToArray( arguments.input, chr(13)&chr(10) ); var numMatches = 0; // Loop over content @@ -36,13 +36,13 @@ component excludeFromHelp=true { if( arguments.expression == '' || reFindNoCase( arguments.expression, line ) ) { if( count ) { numMatches++; - } else { + } else { print.line( line ); } } } - + if( count ) { print.line( numMatches ); } diff --git a/src/cfml/system/modules_app/system-commands/commands/sed.cfc b/src/cfml/system/modules_app/system-commands/commands/sed.cfc index 33c6ae2c9..680777cef 100644 --- a/src/cfml/system/modules_app/system-commands/commands/sed.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/sed.cfc @@ -54,7 +54,7 @@ component { } // Turn output into an array, breaking on carriage returns - var inputLines = listToArray( arguments.inputOrFile, CR ); + var inputLines = listToArray( arguments.inputOrFile, chr(13)&chr(10) ); arguments.commands = trim( arguments.commands ); // Only support a single command right now From 1c17be678bf3e84546e68a4ac8b5146d03ddb64b Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 4 Feb 2020 22:19:48 -0800 Subject: [PATCH 085/102] COMMANDBOX-1099 --- src/cfml/system/Shell.cfc | 182 +++++++++++++++++++------------------- 1 file changed, 89 insertions(+), 93 deletions(-) diff --git a/src/cfml/system/Shell.cfc b/src/cfml/system/Shell.cfc index 2bca4b9cd..ccfd2aa05 100644 --- a/src/cfml/system/Shell.cfc +++ b/src/cfml/system/Shell.cfc @@ -11,7 +11,7 @@ component accessors="true" singleton { // DI property name="commandService" inject="CommandService"; property name="CommandCompletor" inject="CommandCompletor"; - property name="REPLCompletor" inject="REPLCompletor"; + property name="REPLCompletor" inject="REPLCompletor"; property name="readerFactory" inject="ReaderFactory"; property name="print" inject="print"; property name="cr" inject="cr@constants"; @@ -89,7 +89,7 @@ component accessors="true" singleton { boolean asyncLoad=true ){ variables.currentThread = createObject( 'java', 'java.lang.Thread' ).currentThread(); - + // Possible byte order marks variables.BOMS = [ chr( 254 ) & chr( 255 ), @@ -125,7 +125,7 @@ component accessors="true" singleton { } setShellType( 'interactive' ); - + return this; } @@ -183,7 +183,7 @@ component accessors="true" singleton { **/ function getExitCode() { return (createObject( 'java', 'java.lang.System' ).getProperty( 'cfml.cli.exitCode' ) ?: 0); - + } @@ -233,25 +233,25 @@ component accessors="true" singleton { string function ask( message, string mask='', string defaultResponse='', keepHistory=false, highlight=false, complete=false ) { try { - + if( !highlight ) { enableHighlighter( false ); } - + if( !complete ) { enableCompletion( false ); } - + // Some things are best forgotten if( !keepHistory ) { enableHistory( false ); } - + var terminal = getReader().getTerminal(); if( terminal.paused() ) { terminal.resume(); } - + // read reponse while masking input var input = variables.reader.readLine( // Prompt for the user @@ -262,13 +262,13 @@ component accessors="true" singleton { // Optionally pre-fill a default response for them len( arguments.defaultResponse ) ? javacast( "String", arguments.defaultResponse ) : javacast( "null", '' ) ); - + // user wants to exit this command, they've pressed Ctrl-C } catch( org.jline.reader.UserInterruptException var e ) { throw( message='CANCELLED', type="UserInterruptException"); // user wants to exit the entire shell, they've pressed Ctrl-D } catch( org.jline.reader.EndOfFileException var e ) { - // This should probably just read what is currently on the buffer, + // This should probably just read what is currently on the buffer, // but JLine doesn't give me a way to get that currently throw( message='EOF', type="EndOfFileException"); } finally{ @@ -276,11 +276,11 @@ component accessors="true" singleton { setPrompt(); // Turn history back on enableHistory(); - + if( !complete ) { enableCompletion( true ); } - + if( !highlight ) { enableHighlighter( true ); } @@ -319,12 +319,12 @@ component accessors="true" singleton { if( len( arguments.message ) ) { printString( arguments.message ); } - + var terminal = getReader().getTerminal(); if( terminal.paused() ) { terminal.resume(); } - + var keys = createObject( 'java', 'org.jline.keymap.KeyMap' ); var capability = createObject( 'java', 'org.jline.utils.InfoCmp$Capability' ); var bindingReader = createObject( 'java', 'org.jline.keymap.BindingReader' ).init( terminal.reader() ); @@ -335,22 +335,22 @@ component accessors="true" singleton { keys.bind( capability.key_up.name(), keys.key( terminal, capability.key_up ) ); keys.bind( capability.key_down.name(), keys.key( terminal, capability.key_down ) ); keys.bind( capability.back_tab.name(), keys.key( terminal, capability.back_tab ) ); - + // Home/end keys.bind( capability.key_home.name(), keys.key( terminal, capability.key_home ) ); keys.bind( capability.key_end.name(), keys.key( terminal, capability.key_end ) ); - + // delete key/delete line/backspace keys.bind( capability.key_dc.name(), keys.key( terminal, capability.key_dc ) ); // Not sure why, but throwing unsupported exception on Linux // keys.bind( capability.key_backspace.name(), keys.key( terminal, capability.key_backspace ) ); - + keys.bind( capability.key_ic.name(), keys.key( terminal, capability.key_ic ) ); - + // Page up/down keys.bind( capability.key_npage.name(), keys.key( terminal, capability.key_npage ) ); keys.bind( capability.key_ppage.name(), keys.key( terminal, capability.key_ppage ) ); - + // Function keys keys.bind( capability.key_f1.name(), keys.key( terminal, capability.key_f1 ) ); keys.bind( capability.key_f2.name(), keys.key( terminal, capability.key_f2 ) ); @@ -370,19 +370,19 @@ component accessors="true" singleton { // This doesn't seem to work on Windows keys.bind( 'delete', keys.del() ); - + keys.bind( 'escape', keys.esc() ); keys.setAmbiguousTimeout( 50 ); - - + + try { // Next 3 lines required for this to work on *nix attr = terminal.enterRawMode(); terminal.puts( capability.keypad_xmit, [] ); terminal.flush(); - + var binding = bindingReader.readBinding( keys ); - + } catch (any e) { if( e.getPageException().getRootCause().getClass().getName() == 'java.io.InterruptedIOException' ) { throw( message='CANCELLED', type="UserInterruptException"); @@ -391,18 +391,18 @@ component accessors="true" singleton { } finally { // Undo the rawmode stuff above if( !isNull( attr ) ) { - terminal.setAttributes( attr ); + terminal.setAttributes( attr ); } terminal.puts( capability.keypad_local, [] ); - terminal.flush(); + terminal.flush(); } - + if( binding == 'self-insert' ) { key = bindingReader.getLastBinding(); } else { key = binding; } - + // Reset back to default prompt setPrompt(); @@ -516,57 +516,57 @@ component accessors="true" singleton { // while keep running while( variables.keepRunning ){ - + try { - + var interceptData = { prompt : variables.shellPrompt }; getInterceptorService().announceInterception( 'prePrompt', interceptData ); - + if( arguments.silent ) { interceptData.prompt = ''; } - + var terminal = getReader().getTerminal(); if( terminal.paused() ) { terminal.resume(); } - + // Shell stops on this line while waiting for user input if( arguments.silent ) { line = variables.reader.readLine( interceptData.prompt, javacast( "char", ' ' ) ); } else { line = variables.reader.readLine( interceptData.prompt ); } - + // User hits Ctrl-C. Don't let them exit the shell. } catch( org.jline.reader.UserInterruptException var e ) { variables.reader.getTerminal().writer().print( variables.print.yellowLine( 'Use the "exit" command or Ctrl-D to leave this shell.' ) ); variables.reader.getTerminal().writer().flush(); continue; - + // User hits Ctrl-D. Murder the shell dead. } catch( org.jline.reader.EndOfFileException var e ) { - + // Only output this if a user presses Ctrl-D, EOF can also happen if piping an actual file of input into the shell. if( !arguments.silent ) { variables.reader.getTerminal().writer().print( variables.print.boldGreenLine( 'Goodbye!' ) ); variables.reader.getTerminal().writer().flush(); } - + variables.keepRunning = false; continue; - + // Catch all for custom user interrupt thrown from CFML } catch( any var e ) { - + if( e.type.toString() == 'UserInterruptException' ) { - continue; + continue; } else { rethrow; } - + } - + // If the standard input isn't avilable, bail. This happens // when commands are piped in and we've reached the end of the piped stream if( !isDefined( 'line' ) ) { @@ -587,9 +587,9 @@ component accessors="true" singleton { } interceptorService.announceInterception( 'preProcessLine', interceptData ); line = interceptData.line; - + callCommand( command=line, initialCommand=true ); - + interceptorService.announceInterception( 'postProcessLine', interceptData ); } @@ -604,7 +604,7 @@ component accessors="true" singleton { /** * Shutdown the shell and close/release any resources associated. - * This isn't guaranteed to run if the shell is closed, but it + * This isn't guaranteed to run if the shell is closed, but it * will run for a reload command */ function shutdown() { @@ -631,46 +631,46 @@ component accessors="true" singleton { /** * @filePath The path to the history file to set - * + * * Use this wrapper method to change the history file in use by the shell. */ function setHistory( filePath ) { - + var LineReader = createObject( "java", "org.jline.reader.LineReader" ); - + // Save current file variables.reader.getHistory().save(); // Swap out the file setting variables.reader.setVariable( LineReader.HISTORY_FILE, filePath ); // Load in the new file variables.reader.getHistory().load(); - + } /** * @enable Pass true to enable, false to disable - * + * * Enable or disables history in the shell */ function enableHistory( boolean enable=true ) { - + var LineReader = createObject( "java", "org.jline.reader.LineReader" ); - + // Swap out the file setting variables.reader.setVariable( LineReader.DISABLE_HISTORY, !enable ); } /** * @enable Pass true to enable, false to disable - * + * * Enable or disables tab completion in the shell */ function enableCompletion( boolean enable=true ) { - + // DOESN'T WORK. NOT IMPLEMENTED IN JLINE! //var LineReader = createObject( "java", "org.jline.reader.LineReader" ); // variables.reader.setVariable( LineReader.DISABLE_COMPLETION, !enable ); - + if( enable ) { setCompletor( 'command' ); } else { @@ -681,20 +681,20 @@ component accessors="true" singleton { /** * @CompletorName Pass "command", "repl", or "dummy" * @executor If using REPL completor, pass an optional executor for better completion results - * + * * Set the shell's completor */ function setCompletor( string completorName, any executor ) { if( completorName == 'command' ) { - variables.reader.setCompleter( createDynamicProxy( CommandCompletor, [ 'org.jline.reader.Completer' ] ) ); + variables.reader.setCompleter( createDynamicProxy( CommandCompletor, [ 'org.jline.reader.Completer' ] ) ); } else if( completorName == 'repl' ) { - + REPLCompletor.setCurrentExecutor( arguments.executor ?: '' ); - var thisCompletor = createDynamicProxy( REPLCompletor, [ 'org.jline.reader.Completer' ] ); + var thisCompletor = createDynamicProxy( REPLCompletor, [ 'org.jline.reader.Completer' ] ); variables.reader.setCompleter( thisCompletor ); - + } else if( completorName == 'dummy' ) { - variables.reader.setCompleter( createObject( 'java', 'org.jline.reader.impl.completer.NullCompleter' ) ); + variables.reader.setCompleter( createObject( 'java', 'org.jline.reader.impl.completer.NullCompleter' ) ); } else { throw( 'Invalid completor name [#completorName#]. Valid names are "command", "repl", or "dummy".' ); } @@ -702,12 +702,12 @@ component accessors="true" singleton { /** * @enable Pass true to enable, false to disable - * + * * Enable or disables highlighting in the shell */ function enableHighlighter( boolean enable=true ) { if( enable ) { - setHighlighter( 'command' ); + setHighlighter( 'command' ); } else { // A dummy highlighter, or at least one that never seems to do anything... setHighlighter( 'dummy' ); @@ -716,14 +716,14 @@ component accessors="true" singleton { /** * @highlighterName Pass "command", "repl", or "dummy" - * + * * Set the shell's highlighter */ function setHighlighter( string highlighterName ) { if( highlighterName == 'command' ) { - variables.reader.setHighlighter( createDynamicProxy( CommandHighlighter, [ 'org.jline.reader.Highlighter' ] ) ); + variables.reader.setHighlighter( createDynamicProxy( CommandHighlighter, [ 'org.jline.reader.Highlighter' ] ) ); } else if( highlighterName == 'repl' ) { - variables.reader.setHighlighter( createDynamicProxy( REPLHighlighter, [ 'org.jline.reader.Highlighter' ] ) ); + variables.reader.setHighlighter( createDynamicProxy( REPLHighlighter, [ 'org.jline.reader.Highlighter' ] ) ); } else if( highlighterName == 'dummy' ) { variables.reader.setHighlighter( createObject( 'java', 'org.jline.reader.impl.DefaultHighlighter' ) ); } else { @@ -744,7 +744,7 @@ component accessors="true" singleton { returnOutput=false, string piped, boolean initialCommand=false ) { - + var job = wirebox.getInstance( 'interactiveJob' ); var progressBarGeneric = wirebox.getInstance( 'progressBarGeneric' ); @@ -758,7 +758,7 @@ component accessors="true" singleton { // Flush history buffer to disk. I could do this in the quit command // but then I would lose everything if the user just closes the window variables.reader.getHistory().save(); - + try{ if( isArray( command ) ) { @@ -777,12 +777,12 @@ component accessors="true" singleton { if( !initialCommand ) { rethrow; } else { - + progressBarGeneric.clear(); if( job.isActive() ) { job.errorRemaining(); } - + printError( { message : e.message, detail: e.detail } ); } // This type of error means the user hit Ctrl-C, during a readLine() call. Duck out and move along. @@ -791,7 +791,7 @@ component accessors="true" singleton { if( !initialCommand ) { rethrow; } else { - + progressBarGeneric.clear(); if( job.isActive() ) { job.errorRemaining(); @@ -800,34 +800,34 @@ component accessors="true" singleton { variables.reader.getTerminal().writer().println(); variables.reader.getTerminal().writer().print( variables.print.boldRedLine( 'CANCELLED' ) ); } - - } catch (any e) { + + } catch (any e) { // If this is a nested command, pass the exception along to unwind the entire stack. if( !initialCommand ) { rethrow; // This type of error means the user hit Ctrl-C, when not in a readLine() call (and hit my custom signal handler). Duck out and move along. - } else if( e.getPageException().getRootCause().getClass().getName() == 'java.lang.InterruptedException' - || e.type.toString() == 'UserInterruptException' - || e.message == 'UserInterruptException' + } else if( e.getPageException().getRootCause().getClass().getName() == 'java.lang.InterruptedException' + || e.type.toString() == 'UserInterruptException' + || e.message == 'UserInterruptException' || e.type.toString() == 'EndOfFileException' ) { - progressBarGeneric.clear(); + progressBarGeneric.clear(); if( job.isActive() ) { job.errorRemaining(); } - + variables.reader.getTerminal().writer().flush(); variables.reader.getTerminal().writer().println(); - variables.reader.getTerminal().writer().print( variables.print.boldRedLine( 'CANCELLED' ) ); + variables.reader.getTerminal().writer().print( variables.print.boldRedLine( 'CANCELLED' ) ); // Anything else is completely unexpected and means boom booms happened-- full stack please. } else { - progressBarGeneric.clear(); + progressBarGeneric.clear(); if( job.isActive() ) { job.errorRemaining( e.message ); variables.reader.getTerminal().writer().println(); } - + printError( e ); } } @@ -856,22 +856,18 @@ component accessors="true" singleton { job.addLog( result ) } else { printString( result ); - + // If the command output text that didn't end with a line break one, add one var lastChar = mid( result, len( result ), 1 ); if( ! ( lastChar == chr( 10 ) || lastChar == chr( 13 ) ) ) { variables.reader.getTerminal().writer().println(); } } - } else { - if( !job.getActive() ) { - variables.reader.getTerminal().writer().println(); - } } return ''; } - + /** * Is the current terminal interactive? @@ -897,9 +893,9 @@ component accessors="true" singleton { Shell function printError( required err ){ // Don't override a non-1 exit code. if( getExitCode() == 0 ) { - setExitCode( 1 ); + setExitCode( 1 ); } - + var verboseErrors = true; try{ verboseErrors = configService.getSetting( 'verboseErrors', false ); @@ -920,9 +916,9 @@ component accessors="true" singleton { if( structKeyExists( arguments.err, 'detail' ) ) { // If there's a tag context, this is likely a Lucee error and therefore has HTML in the detail if( structKeyExists( arguments.err, 'tagcontext' ) ) { - variables.reader.getTerminal().writer().print( variables.print.boldRedText( variables.formatterUtil.HTML2ANSI( arguments.err.detail ) ) ); + variables.reader.getTerminal().writer().print( variables.print.boldRedText( variables.formatterUtil.HTML2ANSI( arguments.err.detail ) ) ); } else { - variables.reader.getTerminal().writer().print( variables.print.boldRedText( arguments.err.detail ) ); + variables.reader.getTerminal().writer().print( variables.print.boldRedText( arguments.err.detail ) ); } variables.reader.getTerminal().writer().println(); } @@ -937,7 +933,7 @@ component accessors="true" singleton { if( verboseErrors ) { variables.reader.getTerminal().writer().print( variables.print.boldCyanText( "#tc.template#: line #tc.line##variables.cr#" )); } else { - variables.reader.getTerminal().writer().print( variables.print.boldCyanText( "#tc.template.replaceNoCase( expandPath( '/CommandBox' ), '' )#: line #tc.line##variables.cr#" )); + variables.reader.getTerminal().writer().print( variables.print.boldCyanText( "#tc.template.replaceNoCase( expandPath( '/CommandBox' ), '' )#: line #tc.line##variables.cr#" )); } if( len( tc.codeprinthtml ) && idx == 1 ){ variables.reader.getTerminal().writer().print( variables.print.text( variables.formatterUtil.HTML2ANSI( tc.codeprinthtml ) ) ); From 9711e0bf8336801d528901ff68cee65ce6eaaa31 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 4 Feb 2020 22:21:51 -0800 Subject: [PATCH 086/102] Final bump for Lucee 5.3.4.77 stable --- build/build.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/build.properties b/build/build.properties index 4da54e32b..e1162be1b 100644 --- a/build/build.properties +++ b/build/build.properties @@ -10,13 +10,13 @@ java.debug=true #dependencies dependencies.dir=${basedir}/lib -cfml.version=5.3.4.73-RC -cfml.loader.version=2.3.6 +cfml.version=5.3.4.77 +cfml.loader.version=2.3.7 cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} lucee.config.version=5.2.4.37 jre.adoptVesionr=openjdk11 -jre.version=jdk-11.0.5+10 +jre.version=jdk-11.0.6+10 launch4j.version=3.12 runwar.version=4.0.0-SNAPSHOT #runwar.version=3.8.1-SNAPSHOT From 444e5cf31a25ea09b87ab7b055fd61869029b4dd Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 4 Feb 2020 22:22:49 -0800 Subject: [PATCH 087/102] Bump for 5.0.0-rc.1 --- build/build.xml | 134 ++++++++++++++++++++++++------------------------ 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/build/build.xml b/build/build.xml index 41cb96cb6..f6ce4e86c 100644 --- a/build/build.xml +++ b/build/build.xml @@ -16,7 +16,7 @@ External Dependencies: - + @@ -148,7 +148,7 @@ External Dependencies: - @@ -246,7 +246,7 @@ External Dependencies: - + @@ -300,42 +300,42 @@ External Dependencies: This is a stable build, let's compress these jars! - + - + - + - + - + - + - - + + @@ -343,93 +343,93 @@ External Dependencies: - + - - - + + + - + - - + + - + - + - + - + - - + + - + - + - - - - - - - + + + + + + + - + - + - + - + - + - + - - + + @@ -590,7 +590,7 @@ External Dependencies: - - + Linux x64 JRE found! Linux x64 JRE not found, downloading artifact - + - - + + - + - + - + Windows x64 JRE found! Windows x64 JRE not found, downloading artifact - + - + - + Windows x32 JRE found! Windows x32 JRE not found, downloading artifact - + - + - + Mac x64 JRE found! Mac x64 JRE not found, downloading artifact - + - + - + - + - + @@ -986,7 +986,7 @@ External Dependencies: - + @@ -1010,7 +1010,7 @@ External Dependencies: - + @@ -1051,7 +1051,7 @@ External Dependencies: - + @@ -1072,14 +1072,14 @@ External Dependencies: runwar not found or snapshot build, getting artifact from ${ortus.repoURL} - + verbose="true" /> - + - + From 05e055f1a07688179cd1a27c259122fd24724e14 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 4 Feb 2020 23:39:40 -0800 Subject: [PATCH 088/102] use https --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index f6ce4e86c..5399b0ada 100644 --- a/build/build.xml +++ b/build/build.xml @@ -1049,7 +1049,7 @@ External Dependencies:
- + From 3274d144184deebf22991a0340cd74d48e64d6ba Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 4 Feb 2020 23:45:41 -0800 Subject: [PATCH 089/102] Try this url --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index 5399b0ada..b558e03de 100644 --- a/build/build.xml +++ b/build/build.xml @@ -1020,7 +1020,7 @@ External Dependencies: - + From 1c59aebfc203f3000eb4fdc2e067761ac1b9b1ed Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 5 Feb 2020 00:15:35 -0800 Subject: [PATCH 090/102] Final runwar bump --- build/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.properties b/build/build.properties index e1162be1b..495dfd633 100644 --- a/build/build.properties +++ b/build/build.properties @@ -18,7 +18,7 @@ lucee.config.version=5.2.4.37 jre.adoptVesionr=openjdk11 jre.version=jdk-11.0.6+10 launch4j.version=3.12 -runwar.version=4.0.0-SNAPSHOT +runwar.version=4.0.1 #runwar.version=3.8.1-SNAPSHOT jline.version=3.13.0 jansi.version=1.18 From 06e0468a9e3127b41ae8b980e28ef8ffa8589d4c Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 5 Feb 2020 00:29:49 -0800 Subject: [PATCH 091/102] remove lucee.jar pack 200 --- build/build.xml | 129 ------------------------------------------------ 1 file changed, 129 deletions(-) diff --git a/build/build.xml b/build/build.xml index b558e03de..c05c25fa9 100644 --- a/build/build.xml +++ b/build/build.xml @@ -301,135 +301,6 @@ External Dependencies: This is a stable build, let's compress these jars! - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 5b16c2e57c3a96d619047e42069e96ce13d967ac Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 5 Feb 2020 00:32:54 -0800 Subject: [PATCH 092/102] runwar version fix --- build/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.properties b/build/build.properties index 495dfd633..f0b448b0d 100644 --- a/build/build.properties +++ b/build/build.properties @@ -18,7 +18,7 @@ lucee.config.version=5.2.4.37 jre.adoptVesionr=openjdk11 jre.version=jdk-11.0.6+10 launch4j.version=3.12 -runwar.version=4.0.1 +runwar.version=4.0.2 #runwar.version=3.8.1-SNAPSHOT jline.version=3.13.0 jansi.version=1.18 From 5cd0d53d0d617f1594ac4319adb4dfaaf27e6732 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 11 Feb 2020 19:12:36 -0600 Subject: [PATCH 093/102] COMMANDBOX-1093 --- src/cfml/system/services/ServerService.cfc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 74f2055a2..86311dd8a 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -981,8 +981,10 @@ component accessors="true" singleton { } openItems.prepend( { 'label':'Site Home', 'action':'openbrowser', 'url': serverInfo.openbrowserURL, 'image' : expandPath('/commandbox/system/config/server-icons/home.png' ) } ); + + openItems.prepend( { "label" : "Server Home", "action" : "openfilesystem", "path" : serverInfo.serverHomeDirectory, "image" : expandPath('/commandbox/system/config/server-icons/folder.png' ) } ); - openItems.prepend( { "label" : "File System", "hotkey" : "B", "action" : "openfilesystem", "path" : serverInfo.appFileSystemPath, "image" : expandPath('/commandbox/system/config/server-icons/folder.png' ) } ); + openItems.prepend( { "label" : "Webroot", "action" : "openfilesystem", "path" : serverInfo.appFileSystemPath, "image" : expandPath('/commandbox/system/config/server-icons/folder.png' ) } ); serverInfo.trayOptions.prepend( { 'label':'Open...', 'items': openItems, "image" : expandPath('/commandbox/system/config/server-icons/open.png' ) } ); From fcbdcee57c367ab8db986c7f70c1df2ac2c2d952 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 17 Feb 2020 16:17:37 -0600 Subject: [PATCH 094/102] Missing comma --- src/cfml/system/config/box.schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfml/system/config/box.schema.json b/src/cfml/system/config/box.schema.json index 285d860dd..7eeddfef6 100644 --- a/src/cfml/system/config/box.schema.json +++ b/src/cfml/system/config/box.schema.json @@ -390,7 +390,7 @@ "description": "A label a spec must have to be ran", "type": "string" } - } + }, "excludes": { "title": "Exclude List", "description": "A list of labels to exclude when running the tests", From 0fa2e3227eb46a03723a590e972f5b2c535cb1eb Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 26 Feb 2020 12:38:21 -0600 Subject: [PATCH 095/102] Upate for brew to use higiher java versions --- build/brew-template.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build/brew-template.rb b/build/brew-template.rb index aa37e17ed..8398c6425 100644 --- a/build/brew-template.rb +++ b/build/brew-template.rb @@ -11,7 +11,7 @@ class Commandbox < Formula bottle :unneeded - depends_on :java => "1.8" + depends_on java: '>= 8' resource "apidocs" do url "@repoPRDURL@/ortussolutions/commandbox/@stable-version@/commandbox-apidocs-@stable-version@.zip" @@ -20,7 +20,6 @@ class Commandbox < Formula def install libexec.install "box" - (bin/"box").write_env_script libexec/"box", Language::Java.java_home_env("1.8") doc.install resource("apidocs") end From 1c4a5fba9bca663e76d7600545ede49dead1331d Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 26 Feb 2020 17:23:09 -0600 Subject: [PATCH 096/102] Bump runwar for latest fix. --- build/build.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/build.properties b/build/build.properties index f0b448b0d..af33b9f70 100644 --- a/build/build.properties +++ b/build/build.properties @@ -11,14 +11,14 @@ java.debug=true #dependencies dependencies.dir=${basedir}/lib cfml.version=5.3.4.77 -cfml.loader.version=2.3.7 +cfml.loader.version=2.3.8 cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} lucee.config.version=5.2.4.37 jre.adoptVesionr=openjdk11 jre.version=jdk-11.0.6+10 launch4j.version=3.12 -runwar.version=4.0.2 +runwar.version=4.0.1 #runwar.version=3.8.1-SNAPSHOT jline.version=3.13.0 jansi.version=1.18 From deaf49b4161fdf0ab464c634b4ae7b2af2af387e Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 27 Feb 2020 09:12:17 -0600 Subject: [PATCH 097/102] use new Lucee build with log file fix --- build/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.properties b/build/build.properties index af33b9f70..081acd847 100644 --- a/build/build.properties +++ b/build/build.properties @@ -10,7 +10,7 @@ java.debug=true #dependencies dependencies.dir=${basedir}/lib -cfml.version=5.3.4.77 +cfml.version=5.3.4.80 cfml.loader.version=2.3.8 cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} From 899fd8676119271c54989f85ef1cf81b0789fc26 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 27 Feb 2020 10:15:03 -0600 Subject: [PATCH 098/102] MIssing autocomplete options --- .../server-commands/commands/server/java/install.cfc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfml/system/modules_app/server-commands/commands/server/java/install.cfc b/src/cfml/system/modules_app/server-commands/commands/server/java/install.cfc index 413b561ec..a22141044 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/java/install.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/java/install.cfc @@ -28,7 +28,7 @@ component aliases='java install' { /** * @ID Full name of the Java install you wish to remove - * @id.options openjdk8,openjdk9,openjdk10,openjdk11 + * @id.options openjdk8,openjdk9,openjdk10,openjdk11,openjdk12,openjdk13 * @verbse Show verbose installation information * @setDefault Set this Java install to be the default after installing */ From e3329057d9dd9c601a2d0178033538bd91dfdc37 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 2 Mar 2020 13:41:03 -0600 Subject: [PATCH 099/102] Missing Runwar fix for CommandBox-1104 --- build/build.properties | 5 ++--- .../modules_app/forgebox-commands/commands/forgebox/show.cfc | 4 +++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/build/build.properties b/build/build.properties index 081acd847..46734c76a 100644 --- a/build/build.properties +++ b/build/build.properties @@ -11,15 +11,14 @@ java.debug=true #dependencies dependencies.dir=${basedir}/lib cfml.version=5.3.4.80 -cfml.loader.version=2.3.8 +cfml.loader.version=2.3.9 cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} lucee.config.version=5.2.4.37 jre.adoptVesionr=openjdk11 jre.version=jdk-11.0.6+10 launch4j.version=3.12 -runwar.version=4.0.1 -#runwar.version=3.8.1-SNAPSHOT +runwar.version=4.0.5 jline.version=3.13.0 jansi.version=1.18 jgit.version=5.5.1.201910021850-r diff --git a/src/cfml/system/modules_app/forgebox-commands/commands/forgebox/show.cfc b/src/cfml/system/modules_app/forgebox-commands/commands/forgebox/show.cfc index 7729c7120..02dfd8201 100644 --- a/src/cfml/system/modules_app/forgebox-commands/commands/forgebox/show.cfc +++ b/src/cfml/system/modules_app/forgebox-commands/commands/forgebox/show.cfc @@ -71,7 +71,9 @@ component aliases="show" { var forgebox = oEndpoint.getForgeBox(); var forgeboxOrders = forgebox.ORDER; - print.yellowLine( "Contacting ForgeBox, please wait..." ).toConsole(); + if( !json ) { + print.yellowLine( "Contacting ForgeBox, please wait..." ).toConsole(); + } // Default parameters arguments.type = arguments.type ?: ''; From 8c9b31497131e34d148d9a08e1367222fe2d361b Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 4 Mar 2020 11:18:23 -0600 Subject: [PATCH 100/102] Incorrect brew template --- build/brew-template.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/brew-template.rb b/build/brew-template.rb index 8398c6425..b227d5b94 100644 --- a/build/brew-template.rb +++ b/build/brew-template.rb @@ -19,7 +19,7 @@ class Commandbox < Formula end def install - libexec.install "box" + bin.install "box" doc.install resource("apidocs") end From ffc49ade2a8406c26a126593bd931ea1ec457c3c Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 5 Mar 2020 13:27:19 -0600 Subject: [PATCH 101/102] Tab complete fix --- .../modules_app/server-commands/commands/server/open.cfc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/cfml/system/modules_app/server-commands/commands/server/open.cfc b/src/cfml/system/modules_app/server-commands/commands/server/open.cfc index c0a40ffd9..d54f9326b 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/open.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/open.cfc @@ -18,10 +18,11 @@ component { /** * @URI An additional URI to go to when opening the server browser, else it just opens localhost:port - * @name.hint the short name of the server - * @name.optionsUDF serverNameComplete - * @directory.hint web root for the server - * @serverConfigFile The path to the server's JSON file. + * @URI.optionsFileComplete true + * @name.hint the short name of the server + * @name.optionsUDF serverNameComplete + * @directory.hint web root for the server + * @serverConfigFile The path to the server's JSON file. **/ function run( URI="/", From 8a05f076275d1488ff9fade69914c5d1ed9ccd06 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 5 Mar 2020 13:27:29 -0600 Subject: [PATCH 102/102] Final bump for 5.0.0 --- build/build.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/build.xml b/build/build.xml index c05c25fa9..d17b21ab2 100644 --- a/build/build.xml +++ b/build/build.xml @@ -16,8 +16,8 @@ External Dependencies: - - + +