diff --git a/.gitignore b/.gitignore
index 9266971b3..6637a31e5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,6 @@
# OS + IDE
.DS_Store
-/settings.xml
.project
-.settings/
-.vscode
# Testing Logs
test-harness/logs/*.log
diff --git a/.jsbeautifyrc b/.jsbeautifyrc
deleted file mode 100644
index cc0c245dc..000000000
--- a/.jsbeautifyrc
+++ /dev/null
@@ -1,42 +0,0 @@
-{
- "beautify.language": {
- "js": ["js","json","cfc"],
- "css": ["css", "scss"],
- "html": ["htm", "html", "cfml", "cfm"]
- },
- "brace_style": "collapse",
- "indent_with_tabs": true,
- "indent_size": 4,
- "indent_level": 0,
- "preserve_newlines": true,
- "max_preserve_newlines": 5,
- "html": {
- "allowed_file_extensions": ["htm", "html", "xhtml", "shtml", "xml", "svg", "cfm", "cfml"],
- "indent_scripts": "keep",
- "indent_inner_html": true,
- "indent_body_inner_html": true,
- "indent_handlebars": true,
- "unformatted": ["code", "pre"],
- "content_unformatted": ["pre", "code"],
- "wrap_line_length": 0
- },
- "css": {
- "selector_separator_newline": true
- },
- "js": {
- "break_chained_methods": true,
- "comma_first": false,
- "e4x": false,
- "jslint_happy": true,
- "keep_function_indentation": true,
- "keep_array_indentation": true,
- "space_after_anon_function": false,
- "spaceBeforeConditional": true,
- "space_in_paren": true,
- "space_in_empty_paren": false,
- "space_before_conditional": true,
- "operator_position": "before-newline",
- "unescape_strings": false,
- "wrap_line_length": 0
- }
-}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 000000000..3e4aee550
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,12 @@
+{
+ "cfml.mappings": [
+ {
+ "logicalPath": "/coldbox",
+ "directoryPath": "."
+ },
+ {
+ "logicalPath": "/testbox",
+ "directoryPath": "./testbox"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 000000000..c9d4f8517
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,33 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "Run CommandBox Task",
+ "type": "shell",
+ "command": "box task run ${relativeFile}",
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ },
+ "presentation": {
+ "reveal": "always",
+ "panel": "new"
+ },
+ "problemMatcher": []
+ },
+ {
+ "label": "Run CommandBox Task",
+ "type": "shell",
+ "command": "box testbox run bundles=${relativeFile}",
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ },
+ "presentation": {
+ "reveal": "always",
+ "panel": "new"
+ },
+ "problemMatcher": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/system/async/AsyncManager.cfc b/system/async/AsyncManager.cfc
index e3967dc2a..ae73610b3 100644
--- a/system/async/AsyncManager.cfc
+++ b/system/async/AsyncManager.cfc
@@ -17,17 +17,25 @@
*/
component accessors="true" singleton {
+ /**
+ * --------------------------------------------------------------------------
+ * Properties
+ * --------------------------------------------------------------------------
+ */
+
/**
* A collection of executors you can register in the async manager
* so you can run queues, tasks or even scheduled tasks
*/
property name="executors" type="struct";
- // Static class to Executors: java.util.concurrent.Executors
- this.$executors = new util.Executors();
+ /**
+ * This scheduler can be linked to a ColdBox context
+ */
+ property name="coldbox";
- // Helpers
- variables.IntStream = createObject( "java", "java.util.stream.IntStream" );
+ // Static class to Executors: java.util.concurrent.Executors
+ this.$executors = new coldbox.system.async.executors.ExecutorBuilder();
/**
* Constructor
@@ -35,7 +43,10 @@ component accessors="true" singleton {
* @debug Add debugging logs to System out, disabled by default
*/
AsyncManager function init( boolean debug = false ){
- variables.debug = arguments.debug;
+ variables.System = createObject( "java", "java.lang.System" );
+ variables.debug = arguments.debug;
+
+ // Build out our executors map
variables.executors = {};
return this;
@@ -66,7 +77,7 @@ component accessors="true" singleton {
* @debug Add output debugging
* @loadAppContext Load the CFML App contexts or not, disable if not used
*
- * @return The ColdBox Schedule class to work with the schedule: coldbox.system.async.tasks.Executor
+ * @return The ColdBox Schedule class to work with the schedule: coldbox.system.async.executors.Executor
*/
Executor function newExecutor(
required name,
@@ -106,22 +117,21 @@ component accessors="true" singleton {
switch ( arguments.type ) {
case "fixed": {
arguments.executor = this.$executors.newFixedThreadPool( arguments.threads );
- return new tasks.Executor( argumentCollection = arguments );
+ return new executors.Executor( argumentCollection = arguments );
}
case "cached": {
arguments.executor = this.$executors.newCachedThreadPool();
- return new tasks.Executor( argumentCollection = arguments );
+ return new executors.Executor( argumentCollection = arguments );
}
case "single": {
arguments.executor = this.$executors.newFixedThreadPool( 1 );
- return new tasks.Executor( argumentCollection = arguments );
+ return new executors.Executor( argumentCollection = arguments );
}
case "scheduled": {
arguments.executor = this.$executors.newScheduledThreadPool( arguments.threads );
- return new tasks.ScheduledExecutor( argumentCollection = arguments );
+ return new executors.ScheduledExecutor( argumentCollection = arguments );
}
- default : {
-
+ default: {
}
}
throw(
@@ -175,7 +185,7 @@ component accessors="true" singleton {
* @name The executor name
*
* @throws ExecutorNotFoundException
- * @return The executor object: coldbox.system.async.tasks.Executor
+ * @return The executor object: coldbox.system.async.executors.Executor
*/
Executor function getExecutor( required name ){
if ( hasExecutor( arguments.name ) ) {
@@ -295,7 +305,7 @@ component accessors="true" singleton {
boolean debug = false,
boolean loadAppContext = true
){
- return new Future( argumentCollection = arguments );
+ return new tasks.Future( argumentCollection = arguments );
}
/**
@@ -312,7 +322,7 @@ component accessors="true" singleton {
boolean debug = false,
boolean loadAppContext = true
){
- return new Future( argumentCollection = arguments );
+ return new tasks.Future( argumentCollection = arguments );
}
/****************************************************************
@@ -344,6 +354,31 @@ component accessors="true" singleton {
* Utilities *
****************************************************************/
+ /**
+ * Build out a scheduler object for usage within this async manager context and return it to you.
+ * You must manage it's persistence, we only wire it and create it for you so you can use it
+ * to schedule tasks.
+ *
+ * @name The unique name for the scheduler
+ */
+ Scheduler function newScheduler( required name ){
+ return new coldbox.system.async.tasks.Scheduler( arguments.name, this );
+ }
+
+ /**
+ * Build out a new Duration class
+ */
+ Duration function duration(){
+ return new time.Duration( argumentCollection = arguments );
+ }
+
+ /**
+ * Build out a new Period class
+ */
+ Period function period(){
+ return new time.Period( argumentCollection = arguments );
+ }
+
/**
* Build an array out of a range of numbers or using our range syntax.
* You can also build negative ranges
@@ -357,22 +392,45 @@ component accessors="true" singleton {
* @from The initial index, defaults to 1 or you can use the {start}..{end} notation
* @to The last index item
*/
- array function arrayRange( any from=1, numeric to ){
+ array function arrayRange( any from = 1, numeric to ){
// shortcut notation
- if( find( "..", arguments.from ) ){
- arguments.to = getToken( arguments.from, 2, ".." );
- arguments.from = getToken( arguments.from, 1, ".." );
+ if ( find( "..", arguments.from ) ) {
+ arguments.to = getToken( arguments.from, 2, ".." );
+ arguments.from = getToken( arguments.from, 1, ".." );
}
// cap to if larger than from
- if( arguments.to < arguments.from ){
+ if ( arguments.to < arguments.from ) {
arguments.to = arguments.from;
}
// build it up
- return IntStream
+ var javaArray = createObject( "java", "java.util.stream.IntStream" )
.rangeClosed( arguments.from, arguments.to )
.toArray();
+ var cfArray = [];
+ cfArray.append( javaArray, true );
+ return cfArray;
+ }
+
+ /**
+ * Utility to send to output to the output stream
+ *
+ * @var Variable/Message to send
+ */
+ AsyncManager function out( required var ){
+ variables.System.out.println( arguments.var.toString() );
+ return this;
+ }
+
+ /**
+ * Utility to send to output to the error stream
+ *
+ * @var Variable/Message to send
+ */
+ AsyncManager function err( required var ){
+ variables.System.err.println( arguments.var.toString() );
+ return this;
}
-}
\ No newline at end of file
+}
diff --git a/system/async/tasks/Executor.cfc b/system/async/executors/Executor.cfc
similarity index 97%
rename from system/async/tasks/Executor.cfc
rename to system/async/executors/Executor.cfc
index f53422931..42d302837 100644
--- a/system/async/tasks/Executor.cfc
+++ b/system/async/executors/Executor.cfc
@@ -21,7 +21,7 @@ component accessors="true" singleton {
property name="native";
// Prepare the static time unit class
- this.$timeUnit = new coldbox.system.async.util.TimeUnit();
+ this.$timeUnit = new coldbox.system.async.time.TimeUnit();
/**
* Constructor
@@ -71,7 +71,7 @@ component accessors="true" singleton {
);
// Send for execution
- return new FutureTask( variables.native.submit( jCallable ) );
+ return new coldbox.system.async.tasks.FutureTask( variables.native.submit( jCallable ) );
}
/****************************************************************
diff --git a/system/async/util/Executors.cfc b/system/async/executors/ExecutorBuilder.cfc
similarity index 100%
rename from system/async/util/Executors.cfc
rename to system/async/executors/ExecutorBuilder.cfc
diff --git a/system/async/tasks/ScheduledExecutor.cfc b/system/async/executors/ScheduledExecutor.cfc
similarity index 84%
rename from system/async/tasks/ScheduledExecutor.cfc
rename to system/async/executors/ScheduledExecutor.cfc
index 77af2e90f..ba82a12b5 100644
--- a/system/async/tasks/ScheduledExecutor.cfc
+++ b/system/async/executors/ScheduledExecutor.cfc
@@ -12,11 +12,7 @@
* use to monitor and get results from the tasks at hand, if any.
*
*/
-component
- extends ="Executor"
- accessors="true"
- singleton
-{
+component extends="Executor" accessors="true" singleton {
/****************************************************************
* Scheduling Methods *
@@ -49,9 +45,9 @@ component
// build out the java callable
var jCallable = createDynamicProxy(
new coldbox.system.async.proxies.Callable(
- supplier = arguments.task,
- method = arguments.method,
- debug = variables.debug,
+ supplier = arguments.task,
+ method = arguments.method,
+ debug = variables.debug,
loadAppContext = variables.loadAppContext
),
[ "java.util.concurrent.Callable" ]
@@ -65,7 +61,7 @@ component
);
// Return the results
- return new ScheduledFuture( jScheduledFuture );
+ return new coldbox.system.async.tasks.ScheduledFuture( jScheduledFuture );
}
/**
@@ -106,7 +102,7 @@ component
);
// Return the results
- return new ScheduledFuture( jScheduledFuture );
+ return new coldbox.system.async.tasks.ScheduledFuture( jScheduledFuture );
}
/**
@@ -145,16 +141,39 @@ component
);
// Return the results
- return new ScheduledFuture( jScheduledFuture );
+ return new coldbox.system.async.tasks.ScheduledFuture( jScheduledFuture );
}
/****************************************************************
* Builder Methods *
****************************************************************/
+ /**
+ * Build out a new scheduled task
+ *
+ * @deprecated DO NOT USE, use newTask() instead
+ *
+ * @task The closure or cfc that represents the task
+ * @method The method on the cfc to call, defaults to "run" (optional)
+ */
ScheduledTask function newSchedule( required task, method = "run" ){
+ return this.newTask( argumentCollection = arguments );
+ }
+
+ /**
+ * Build out a new scheduled task representation. Calling this method does not mean that the task is executed.
+ *
+ * @name The name of the task
+ * @task The closure or cfc that represents the task (optional)
+ * @method The method on the cfc to call, defaults to "run" (optional)
+ */
+ ScheduledTask function newTask(
+ name = "task-#getName()#-#createUUID()#",
+ task,
+ method = "run"
+ ){
arguments.executor = this;
- return new ScheduledTask( argumentCollection = arguments );
+ return new coldbox.system.async.tasks.ScheduledTask( argumentCollection = arguments );
}
/****************************************************************
@@ -172,9 +191,9 @@ component
function buildJavaRunnable( required task, required method ){
return createDynamicProxy(
new coldbox.system.async.proxies.Runnable(
- target = arguments.task,
- method = arguments.method,
- debug = variables.debug,
+ target = arguments.task,
+ method = arguments.method,
+ debug = variables.debug,
loadAppContext = variables.loadAppContext
),
[ "java.lang.Runnable" ]
diff --git a/system/async/proxies/BaseProxy.cfc b/system/async/proxies/BaseProxy.cfc
index 826333b93..cecee5fb9 100644
--- a/system/async/proxies/BaseProxy.cfc
+++ b/system/async/proxies/BaseProxy.cfc
@@ -109,17 +109,28 @@ component accessors="true" {
if ( server.keyExists( "lucee" ) ) {
getCFMLContext().setApplicationContext( variables.cfContext );
} else {
+ // Set the current thread's class loader from the CF space to avoid
+ // No class defined issues in thread land.
+ getCurrentThread().setContextClassLoader( variables.originalFusionContext.getClass().getClassLoader() );
+
+ // Prepare a new context in ACF for the thread
var fusionContext = variables.originalFusionContext.clone();
+ // Create a new page context for the thread
var pageContext = variables.originalPageContext.clone();
+ // Reset it's scopes, else bad things happen
pageContext.resetLocalScopes();
+ // Set the cf context into it
+ pageContext.setFusionContext( fusionContext );
+ fusionContext.pageContext = pageContext;
+ fusionContext.SymTab_setApplicationScope( variables.originalAppScope );
+
+ // Create a fake page to run this thread in and link it to the fake page context and fusion context
var page = variables.originalPage._clone();
page.pageContext = pageContext;
fusionContext.parent = page;
+ // Set the current context of execution now
variables.fusionContextStatic.setCurrent( fusionContext );
- fusionContext.pageContext = pageContext;
- fusionContext.SymTab_setApplicationScope( variables.originalAppScope );
- pageContext.setFusionContext( fusionContext );
pageContext.initializeWith(
page,
pageContext,
diff --git a/system/async/proxies/Runnable.cfc b/system/async/proxies/Runnable.cfc
index 6315d332d..50965f13d 100644
--- a/system/async/proxies/Runnable.cfc
+++ b/system/async/proxies/Runnable.cfc
@@ -16,7 +16,7 @@ component extends="BaseProxy" {
required target,
method = "run",
boolean debug = false,
- boolean loadAppContext = true,
+ boolean loadAppContext = true
){
super.init(
arguments.target,
diff --git a/system/async/Future.cfc b/system/async/tasks/Future.cfc
similarity index 90%
rename from system/async/Future.cfc
rename to system/async/tasks/Future.cfc
index 65b76baef..2fbd558d5 100644
--- a/system/async/Future.cfc
+++ b/system/async/tasks/Future.cfc
@@ -39,7 +39,7 @@ component accessors="true" {
property name="futureTimeout" type="struct";
// Prepare the static time unit class
- this.$timeUnit = new util.TimeUnit();
+ this.$timeUnit = new coldbox.system.async.time.TimeUnit();
/**
* Construct a new ColdBox Future backed by a Java Completable Future
@@ -62,15 +62,12 @@ component accessors="true" {
variables.executor = ( isNull( arguments.executor ) ? "" : arguments.executor );
// Are we using a Java or CFML executor?
- if( isObject( variables.executor ) && structKeyExists( variables.executor, "getNative" ) ){
+ if ( isObject( variables.executor ) && structKeyExists( variables.executor, "getNative" ) ) {
variables.executor = variables.executor.getNative();
}
// Prepare initial timeouts
- variables.futureTimeout = {
- "timeout" : 0,
- "timeUnit" : "milliseconds"
- };
+ variables.futureTimeout = { "timeout" : 0, "timeUnit" : "milliseconds" };
// Default the future to be empty
variables.isEmptyFuture = true;
@@ -118,10 +115,14 @@ component accessors="true" {
* @timeout how long to wait before completing normally with the given value, in units of unit
* @timeUnit The time unit to use, available units are: days, hours, microseconds, milliseconds, minutes, nanoseconds, and seconds. The default is milliseconds
*/
- Future function completeOnTimeout( required value, required timeout, timeUnit = "milliseconds" ){
+ Future function completeOnTimeout(
+ required value,
+ required timeout,
+ timeUnit = "milliseconds"
+ ){
variables.native = variables.native.completeOnTimeout(
arguments.value,
- javaCast( "long", arguments.timeout ),
+ javacast( "long", arguments.timeout ),
this.$timeUnit.get( arguments.timeUnit )
);
return this;
@@ -135,7 +136,7 @@ component accessors="true" {
*/
Future function orTimeout( required timeout, timeUnit = "milliseconds" ){
variables.native = variables.native.orTimeout(
- javaCast( "long", arguments.timeout ),
+ javacast( "long", arguments.timeout ),
this.$timeUnit.get( arguments.timeUnit )
);
return this;
@@ -174,7 +175,7 @@ component accessors="true" {
* @defaultValue
*/
Future function completeWithException(){
- return completeExceptionally( argumentCollection=arguments );
+ return completeExceptionally( argumentCollection = arguments );
}
/**
@@ -192,11 +193,11 @@ component accessors="true" {
any function join( defaultValue ){
var results = variables.native.join();
- if( !isNull( results ) ){
+ if ( !isNull( results ) ) {
return results;
}
- if( isNull( results ) && !isNull( arguments.defaultValue ) ){
+ if ( isNull( results ) && !isNull( arguments.defaultValue ) ) {
return arguments.defaultValue;
}
}
@@ -219,12 +220,12 @@ component accessors="true" {
){
// Do we have a timeout?
if ( arguments.timeout != 0 ) {
- try{
+ try {
var results = variables.native.get(
javacast( "long", arguments.timeout ),
this.$timeUnit.get( arguments.timeUnit )
);
- } catch( "java.util.concurrent.TimeoutException" e ){
+ } catch ( "java.util.concurrent.TimeoutException" e ) {
// If we have a result, return it, else rethrow
if ( !isNull( arguments.defaultValue ) ) {
return arguments.defaultValue;
@@ -306,7 +307,7 @@ component accessors="true" {
Future function exceptionally( required target ){
variables.native = variables.native.exceptionally(
createDynamicProxy(
- new proxies.Function(
+ new coldbox.system.async.proxies.Function(
arguments.target,
variables.debug,
variables.loadAppContext
@@ -342,7 +343,7 @@ component accessors="true" {
any executor = variables.executor
){
var jSupplier = createDynamicProxy(
- new proxies.Supplier(
+ new coldbox.system.async.proxies.Supplier(
arguments.supplier,
arguments.method,
variables.debug,
@@ -410,7 +411,7 @@ component accessors="true" {
*/
Future function handle( required action ){
var biFunction = createDynamicProxy(
- new proxies.BiFunction(
+ new coldbox.system.async.proxies.BiFunction(
arguments.action,
variables.debug,
variables.loadAppContext
@@ -448,7 +449,7 @@ component accessors="true" {
*/
Future function handleAsync( required action, executor ){
var biFunction = createDynamicProxy(
- new proxies.BiFunction(
+ new coldbox.system.async.proxies.BiFunction(
arguments.action,
variables.debug,
variables.loadAppContext
@@ -456,7 +457,7 @@ component accessors="true" {
[ "java.util.function.BiFunction" ]
);
- if( !isNull( arguments.executor ) ){
+ if ( !isNull( arguments.executor ) ) {
variables.native = variables.native.handleAsync( biFunction, arguments.executor );
} else {
variables.native = variables.native.handleAsync( biFunction );
@@ -485,7 +486,7 @@ component accessors="true" {
*/
Future function whenComplete( required action ){
var biConsumer = createDynamicProxy(
- new proxies.BiConsumer(
+ new coldbox.system.async.proxies.BiConsumer(
arguments.action,
variables.debug,
variables.loadAppContext
@@ -522,7 +523,7 @@ component accessors="true" {
*/
Future function whenCompleteAsync( required action, executor ){
var biConsumer = createDynamicProxy(
- new proxies.BiConsumer(
+ new coldbox.system.async.proxies.BiConsumer(
arguments.action,
variables.debug,
variables.loadAppContext
@@ -530,7 +531,7 @@ component accessors="true" {
[ "java.util.function.BiConsumer" ]
);
- if( !isNull( arguments.executor ) ){
+ if ( !isNull( arguments.executor ) ) {
variables.native = variables.native.whenCompleteAsync( biConsumer, arguments.executor );
} else {
variables.native = variables.native.whenCompleteAsync( biConsumer );
@@ -559,7 +560,7 @@ component accessors="true" {
*/
Future function then( required target ){
var apply = createDynamicProxy(
- new proxies.Function(
+ new coldbox.system.async.proxies.Function(
arguments.target,
variables.debug,
variables.loadAppContext
@@ -567,7 +568,7 @@ component accessors="true" {
[ "java.util.function.Function" ]
);
- if( variables.isEmptyFuture ){
+ if ( variables.isEmptyFuture ) {
variables.native.thenApply( apply );
} else {
variables.native = variables.native.thenApply( apply );
@@ -606,7 +607,7 @@ component accessors="true" {
*/
Future function thenAsync( required target, executor ){
var apply = createDynamicProxy(
- new proxies.Function(
+ new coldbox.system.async.proxies.Function(
arguments.target,
variables.debug,
variables.loadAppContext
@@ -649,7 +650,7 @@ component accessors="true" {
*/
Future function thenRun( required target ){
var fConsumer = createDynamicProxy(
- new proxies.Consumer(
+ new coldbox.system.async.proxies.Consumer(
arguments.target,
variables.debug,
variables.loadAppContext
@@ -666,7 +667,7 @@ component accessors="true" {
* Alias to thenRun()
*/
Future function thenAccept(){
- return thenRun( argumentCollection=arguments );
+ return thenRun( argumentCollection = arguments );
}
/**
@@ -691,7 +692,7 @@ component accessors="true" {
*/
Future function thenRunAsync( required target, executor ){
var fConsumer = createDynamicProxy(
- new proxies.Consumer(
+ new coldbox.system.async.proxies.Consumer(
arguments.target,
variables.debug,
variables.loadAppContext
@@ -699,7 +700,7 @@ component accessors="true" {
[ "java.util.function.Consumer" ]
);
- if( !isNull( arguments.executor ) ){
+ if ( !isNull( arguments.executor ) ) {
variables.native.thenAcceptAsync( fConsumer, arguments.executor );
} else {
variables.native.thenAcceptAsync( fConsumer );
@@ -712,7 +713,7 @@ component accessors="true" {
* Alias to thenRunAsync()
*/
Future function thenAcceptAsync(){
- return thenRunAsync( argumentCollection=arguments );
+ return thenRunAsync( argumentCollection = arguments );
}
/**
@@ -729,7 +730,7 @@ component accessors="true" {
Future function thenCompose( required fn ){
variables.native = variables.native.thenCompose(
createDynamicProxy(
- new proxies.FutureFunction(
+ new coldbox.system.async.proxies.FutureFunction(
arguments.fn,
variables.debug,
variables.loadAppContext
@@ -751,7 +752,7 @@ component accessors="true" {
variables.native = variables.native.thenCombine(
arguments.future.getNative(),
createDynamicProxy(
- new proxies.BiFunction(
+ new coldbox.system.async.proxies.BiFunction(
arguments.fn,
variables.debug,
variables.loadAppContext
@@ -787,11 +788,10 @@ component accessors="true" {
return this.thenAsync( function(){
// return back the completed array results
return jFutures.map( function( jFuture ){
- //writeDump( var=arguments.jFuture.get(), output="console" );
+ // writeDump( var=arguments.jFuture.get(), output="console" );
return arguments.jFuture.get();
} );
} );
-
}
/**
@@ -816,16 +816,16 @@ component accessors="true" {
* @return An array or struct with the items processed in parallel
*/
any function allApply( items, fn, executor, timeout, timeUnit ){
- var incomingExecutor = arguments.executor ?: "";
+ var incomingExecutor = arguments.executor ?: "";
// Boolean indicator to avoid `isObject()` calls on iterations
- var usingExecutor = isObject( incomingExecutor );
+ var usingExecutor = isObject( incomingExecutor );
// Cast it here, so it only happens once
- var currentTimeout = javacast( "long", arguments.timeout ?: variables.futureTimeout.timeout );
- var currentTimeunit = arguments.timeUnit ?: variables.futureTimeout.timeUnit;
+ var currentTimeout = javacast( "long", arguments.timeout ?: variables.futureTimeout.timeout );
+ var currentTimeunit = arguments.timeUnit ?: variables.futureTimeout.timeUnit;
// Create the function proxy once instead of many times during iterations
var jApply = createDynamicProxy(
- new proxies.Function(
+ new coldbox.system.async.proxies.Function(
arguments.fn,
variables.debug,
variables.loadAppContext
@@ -834,7 +834,7 @@ component accessors="true" {
);
// Array Processing
- if( isArray( arguments.items ) ){
+ if ( isArray( arguments.items ) ) {
// Return the array as a collection of processed values
return arguments.items
// Startup the tasks
@@ -843,63 +843,45 @@ component accessors="true" {
var f = new Future( arguments.thisItem );
// Execute it on a custom executor or core
- if( usingExecutor ){
- return f.setNative(
- f.getNative().thenApplyAsync( jApply, incomingExecutor )
- );
+ if ( usingExecutor ) {
+ return f.setNative( f.getNative().thenApplyAsync( jApply, incomingExecutor ) );
}
// Core Executor
- return f.setNative(
- f.getNative().thenApplyAsync( jApply )
- );
+ return f.setNative( f.getNative().thenApplyAsync( jApply ) );
} )
// Collect the tasks
.map( function( thisFuture ){
- return arguments.thisFuture.get(
- currentTimeout,
- currentTimeunit
- );
+ return arguments.thisFuture.get( currentTimeout, currentTimeunit );
} );
}
// Struct Processing
- else if( isStruct( arguments.items ) ){
+ else if ( isStruct( arguments.items ) ) {
return arguments.items
// Startup the tasks
.map( function( key, value ){
// Create a new completed future
- var f = new Future( {
- "key" : arguments.key,
- "value" : arguments.value
- } );
+ var f = new Future( { "key" : arguments.key, "value" : arguments.value } );
// Execute it on a custom executor or core
- if( usingExecutor ){
- return f.setNative(
- f.getNative().thenApplyAsync( jApply, incomingExecutor )
- );
+ if ( usingExecutor ) {
+ return f.setNative( f.getNative().thenApplyAsync( jApply, incomingExecutor ) );
}
// Core Executor
- return f.setNative(
- f.getNative().thenApplyAsync( jApply )
- );
+ return f.setNative( f.getNative().thenApplyAsync( jApply ) );
} )
// Collect the tasks
.map( function( key, thisFuture ){
- return arguments.thisFuture.get(
- currentTimeout,
- currentTimeunit
- );
+ return arguments.thisFuture.get( currentTimeout, currentTimeunit );
} );
- } else{
+ } else {
throw(
- message : "The collection type passed is not yet supported!",
- type : "UnsupportedCollectionException",
+ message: "The collection type passed is not yet supported!",
+ type : "UnsupportedCollectionException",
detail : getMetadata( arguments.items )
);
}
-
}
/**
@@ -948,27 +930,24 @@ component accessors="true" {
var target = arguments;
// Is the first element an array? Then use that as the builder for workloads
- if( !isNull( arguments[ 1 ] ) && isArray( arguments[ 1 ] ) ){
+ if ( !isNull( arguments[ 1 ] ) && isArray( arguments[ 1 ] ) ) {
target = arguments[ 1 ];
}
// I have to use arrayMap() if not lucee does not use array notation
// but struct notation and order get's lost
- return arrayMap(
- target,
- function( future ){
- // Is this a closure/lambda/udf? Then inflate to a future
- if ( isClosure( arguments.future ) || isCustomFunction( arguments.future ) ) {
- return new Future().run( arguments.future );
- }
- // Return it
- return arguments.future;
+ return arrayMap( target, function( future ){
+ // Is this a closure/lambda/udf? Then inflate to a future
+ if ( isClosure( arguments.future ) || isCustomFunction( arguments.future ) ) {
+ return new Future().run( arguments.future );
}
- ).reduce( function( results, future ){
+ // Return it
+ return arguments.future;
+ } ).reduce( function( results, future ){
// Now process it
results.append( arguments.future.getNative() );
return results;
}, [] );
}
-}
\ No newline at end of file
+}
diff --git a/system/async/tasks/FutureTask.cfc b/system/async/tasks/FutureTask.cfc
index 58d738c41..cef5eb9c3 100644
--- a/system/async/tasks/FutureTask.cfc
+++ b/system/async/tasks/FutureTask.cfc
@@ -13,7 +13,7 @@ component accessors="true" {
property name="native";
// Prepare the static time unit class
- this.$timeUnit = new coldbox.system.async.util.TimeUnit();
+ this.$timeUnit = new coldbox.system.async.time.TimeUnit();
/**
* Build the ColdBox Future with the Java native class
diff --git a/system/async/tasks/ScheduledTask.cfc b/system/async/tasks/ScheduledTask.cfc
index b16b32c32..c6574ef6f 100644
--- a/system/async/tasks/ScheduledTask.cfc
+++ b/system/async/tasks/ScheduledTask.cfc
@@ -1,12 +1,19 @@
+/**
+ * This object represents a scheduled task that will be sent in to a scheduled executor for scheduling.
+ * It has a fluent and human dsl for setting it up and restricting is scheduling and frequency of scheduling.
+ *
+ * A task can be represented as either a closure or a cfc with a `run()` or custom runnable method.
+ */
component accessors="true" {
/**
- * The delay to use in the schedule execution
+ * The delay or time to wait before we execute the task in the scheduler
*/
property name="delay" type="numeric";
/**
- * The period of execution of the tasks in this schedule
+ * A fixed time period of execution of the tasks in this schedule. It does not wait for tasks to finish,
+ * tasks are fired exactly at that time period.
*/
property name="period" type="numeric";
@@ -16,106 +23,1189 @@ component accessors="true" {
property name="spacedDelay" type="numeric";
/**
- * The task to execute
+ * The time unit string used to schedule the task
+ */
+ property name="timeunit";
+
+ /**
+ * The task closure or CFC to execute in the task
*/
property name="task";
/**
- * The method to execute if any
+ * The method to execute if the task is a CFC
*/
property name="method";
+ /**
+ * The human name of this task
+ */
+ property name="name";
+
+ /**
+ * A handy boolean that disables the scheduling of this task
+ */
+ property name="disabled" type="boolean";
+
+ /**
+ * A closure, that if registered, determines if this task will be sent for scheduling or not.
+ * It is both evaluated at scheduling and at runtime.
+ */
+ property name="when" type="any";
+
+ /**
+ * The timezone this task runs under, by default we use the timezone defined in the schedulers
+ */
+ property name="timezone";
+
+ /**
+ * This task can be assigned to a task scheduler or be executed on its own at runtime
+ */
+ property name="scheduler";
+
+ /**
+ * The collection of stats for the task: { created, lastRun, nextRun, totalRuns, totalFailures, totalSuccess, lastResult, neverRun, lastExecutionTime }
+ */
+ property name="stats" type="struct";
+
+ /**
+ * The before task closure
+ */
+ property name="beforeTask";
+
+ /**
+ * The after task closure
+ */
+ property name="afterTask";
+
+ /**
+ * The task success closure
+ */
+ property name="onTaskSuccess";
+
+ /**
+ * The task failure closure
+ */
+ property name="onTaskFailure";
+
+ /**
+ * The constraint of what day of the month we need to run on: 1-31
+ */
+ property name="dayOfTheMonth" type="numeric";
+
+ /**
+ * The constraint of what day of the week this runs on: 1-7
+ */
+ property name="dayOfTheWeek" type="numeric";
+
+ /**
+ * Constraint to run only on weekends
+ */
+ property name="weekends" type="boolean";
+
+ /**
+ * Constraint to run only on weekdays
+ */
+ property name="weekdays" type="boolean";
+
+ /**
+ * Constraint to run only on the last business day of the month
+ */
+ property name="lastBusinessDay" type="boolean";
+
+ /**
+ * By default tasks execute in an interval frequency which can cause overlaps if tasks
+ * take longer than their periods. With this boolean flag turned on, the schedulers
+ * don't kick off the intervals until the tasks finish executing. Meaning no overlaps.
+ */
+ property name="noOverlaps" type="boolean";
+
+ /**
+ * Get the ColdBox utility object
+ */
+ property name="util";
+
/**
* Constructor
*
- * @task
- * @method
+ * @name The name of this task
+ * @executor The executor this task will run under and be linked to
+ * @task The closure or cfc that represents the task (optional)
+ * @method The method on the cfc to call, defaults to "run" (optional)
*/
ScheduledTask function init(
- required task,
+ required name,
required executor,
- method = "run"
+ any task = "",
+ method = "run"
){
- variables.task = arguments.task;
- variables.executor = arguments.executor;
- variables.method = arguments.method;
-
+ // Utility class
+ variables.util = new coldbox.system.core.util.Util();
+ // Link up the executor and name
+ variables.executor = arguments.executor;
+ variables.name = arguments.name;
+ // time unit helper
+ variables.chronoUnitHelper = new coldbox.system.async.time.ChronoUnit();
+ variables.timeUnitHelper = new coldbox.system.async.time.TimeUnit();
+ // System Helper
+ variables.System = createObject( "java", "java.lang.System" );
// Init Properties
- variables.period = 0;
- variables.delay = 0;
- variables.spacedDelay = 0;
- variables.timeUnit = "milliseconds";
+ variables.task = arguments.task;
+ variables.method = arguments.method;
+ // Default Frequencies
+ variables.period = 0;
+ variables.delay = 0;
+ variables.spacedDelay = 0;
+ variables.timeUnit = "milliseconds";
+ variables.noOverlap = false;
+ // Constraints
+ variables.disabled = false;
+ variables.when = "";
+ variables.dayOfTheMonth = 0;
+ variables.dayOfTheWeek = 0;
+ variables.weekends = false;
+ variables.weekdays = false;
+ variables.lastBusinessDay = false;
+ variables.noOverlaps = false;
+ // Probable Scheduler or not
+ variables.scheduler = "";
+ // Prepare execution tracking stats
+ variables.stats = {
+ // Save name just in case
+ "name" : arguments.name,
+ // When task got created
+ "created" : now(),
+ // The last execution run timestamp
+ "lastRun" : "",
+ // When's the next execution
+ "nextRun" : "",
+ // Total runs
+ "totalRuns" : 0,
+ // Total faiulres
+ "totalFailures" : 0,
+ // Total successful task executions
+ "totalSuccess" : 0,
+ // How long the last execution took
+ "lastExecutionTime" : 0,
+ // The latest result if any
+ "lastResult" : "",
+ // If the task has never ran or not
+ "neverRun" : true,
+ // Server Host
+ "inetHost" : variables.util.discoverInetHost(),
+ // Server IP
+ "localIp" : variables.util.getServerIp()
+ };
+ // Life cycle methods
+ variables.beforeTask = "";
+ variables.afterTask = "";
+ variables.onTaskSuccess = "";
+ variables.onTaskFailure = "";
+
+ return this;
+ }
+
+ /**
+ * --------------------------------------------------------------------------
+ * Utility and Operational
+ * --------------------------------------------------------------------------
+ */
+
+ /**
+ * Utility to send to output to the output stream
+ *
+ * @var Variable/Message to send
+ */
+ ScheduledTask function out( required var ){
+ variables.System.out.println( arguments.var.toString() );
+ return this;
+ }
+
+ /**
+ * Utility to send to output to the error stream
+ *
+ * @var Variable/Message to send
+ */
+ ScheduledTask function err( required var ){
+ variables.System.err.println( arguments.var.toString() );
+ return this;
+ }
+
+ /**
+ * Set the timezone for this task using the task identifier else we default to our scheduler
+ *
+ * @see https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/ZoneId.html
+ *
+ * @timezone The timezone string identifier
+ */
+ ScheduledTask function setTimezone( required timezone ){
+ variables.timezone = createObject( "java", "java.time.ZoneId" ).of( arguments.timezone );
+ return this;
+ }
+
+ /**
+ * Has this task been assigned to a scheduler or not?
+ */
+ boolean function hasScheduler(){
+ return isObject( variables.scheduler );
+ }
+
+ /**
+ * This method is used to register the callable closure or cfc on this scheduled task.
+ *
+ * @task The closure or cfc that represents the task
+ * @method The method on the cfc to call, defaults to "run" (optional)
+ *
+ * @return The schedule with the task/method registered on it
+ */
+ ScheduledTask function call( required task, method = "run" ){
+ variables.task = arguments.task;
+ variables.method = arguments.method;
+ return this;
+ }
+
+ /**
+ * --------------------------------------------------------------------------
+ * Restrictions
+ * --------------------------------------------------------------------------
+ */
+ /**
+ * Register a when closure that will be executed before the task is set to be registered.
+ * If the closure returns true we schedule, else we disable it.
+ */
+ ScheduledTask function when( target ){
+ variables.when = arguments.target;
+ return this;
+ }
+
+ /**
+ * Disable the task when scheduled, meaning, don't run this sucker!
+ */
+ ScheduledTask function disable(){
+ variables.disabled = true;
+ return this;
+ }
+
+ /**
+ * Enable the task when disabled so we can run again
+ */
+ ScheduledTask function enable(){
+ variables.disabled = false;
return this;
}
+ /**
+ * Verifies if we can schedule this task or not by looking at the following constraints:
+ *
+ * - disabled
+ * - when closure
+ */
+ boolean function isDisabled(){
+ return variables.disabled;
+ }
+
+ /**
+ * --------------------------------------------------------------------------
+ * Startup and Runnable Proxy
+ * --------------------------------------------------------------------------
+ */
+
+ /**
+ * This method verifies if the running task is constrained to run on specific valid constraints:
+ *
+ * - when
+ * - dayOfTheMonth
+ * - dayOfTheWeek
+ * - lastBusinessDay
+ * - weekends
+ * - weekdays
+ *
+ * This method is called by the `run()` method at runtime to determine if the task can be ran at that point in time
+ */
+ boolean function isConstrained(){
+ var now = getJavaNow();
+
+ // When Closure that dictates if the task can be scheduled/ran: true => yes, false => no
+ if ( isClosure( variables.when ) && !variables.when( this ) ) {
+ return true;
+ }
+
+ // Do we have a day of the month constraint? and the same as the running date/time? Else skip it
+ if (
+ variables.dayOfTheMonth > 0 &&
+ now.getDayOfMonth() != variables.dayOfTheMonth
+ ) {
+ return true;
+ }
+
+ // Do we have a last business day constraint
+ if (
+ variables.lastBusinessDay &&
+ now.getDayOfMonth() != getLastDayOfTheMonth().getDayOfMonth()
+ ) {
+ return true;
+ }
+
+ // Do we have weekends?
+ if (
+ variables.weekends &&
+ now.getDayOfWeek().getValue() <= 5
+ ) {
+ return true;
+ }
+
+ // Do we have weekdays?
+ if (
+ variables.weekdays &&
+ now.getDayOfWeek().getValue() > 5
+ ) {
+ return true;
+ }
+
+ // Do we have day of the week?
+ if (
+ variables.dayOfTheWeek > 0 &&
+ now.getDayOfWeek().getValue() != variables.dayOfTheWeek
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * This is the runnable proxy method that executes your code by the executors
+ */
+ function run(){
+ var sTime = getTickCount();
+
+ // If disabled or paused
+ if ( isDisabled() ) {
+ return;
+ }
+
+ // Check for constraints of execution
+ if ( isConstrained() ) {
+ return;
+ }
+
+ // Mark the task as it wil run now for the first time
+ variables.stats.neverRun = false;
+
+ try {
+ // Before Interceptors
+ if ( hasScheduler() ) {
+ getScheduler().beforeAnyTask( this );
+ }
+ if ( isClosure( variables.beforeTask ) ) {
+ variables.beforeTask( this );
+ }
+
+ // Target task call callable
+ if ( isClosure( variables.task ) || isCustomFunction( variables.task ) ) {
+ variables.stats.lastResult = variables.task() ?: "";
+ } else {
+ variables.stats.lastResult = invoke( variables.task, variables.method ) ?: "";
+ }
+
+ // After Interceptor
+ if ( isClosure( variables.afterTask ) ) {
+ variables.afterTask( this, variables.stats.lastResult );
+ }
+ if ( hasScheduler() ) {
+ getScheduler().afterAnyTask( this, variables.stats.lastResult );
+ }
+
+ // store successes and call success interceptor
+ variables.stats.totalSuccess = variables.stats.totalSuccess + 1;
+ if ( isClosure( variables.onTaskSuccess ) ) {
+ variables.onTaskSuccess( this, variables.stats.lastResult );
+ }
+ if ( hasScheduler() ) {
+ getScheduler().onAnyTaskSuccess( this, variables.stats.lastResult );
+ }
+ } catch ( any e ) {
+ // store failures
+ variables.stats.totalFailures = variables.stats.totalFailures + 1;
+ // Life Cycle
+ if ( isClosure( variables.onTaskFailure ) ) {
+ variables.onTaskFailure( this, e );
+ }
+ if ( hasScheduler() ) {
+ getScheduler().onAnyTaskError( this, e );
+ }
+ } finally {
+ // Store finalization stats
+ variables.stats.lastRun = now();
+ variables.stats.totalRuns = variables.stats.totalRuns + 1;
+ variables.stats.lastExecutionTime = getTickCount() - sTime;
+ // Call internal cleanups event
+ cleanupTaskRun();
+ }
+ }
+
+ /**
+ * This method is called ALWAYS after a task runs, wether in failure or success but used internally for
+ * any type of cleanups
+ */
+ function cleanupTaskRun(){
+ // no cleanups for now
+ }
+
+ /**
+ * This method registers the task into the executor and sends it for execution and scheduling.
+ * This will not register the task for execution if the disabled flag or the constraints allow it.
+ *
+ * @return A ScheduledFuture from where you can monitor the task, an empty ScheduledFuture if the task was not registered
+ */
ScheduledFuture function start(){
+ // If we have overlaps and the spaced delay is 0 then grab it from the period
+ if ( variables.noOverlaps and variables.spacedDelay eq 0 ) {
+ variables.spacedDelay = variables.period;
+ }
+
+ // Startup a spaced frequency task: no overlaps
if ( variables.spacedDelay > 0 ) {
return variables.executor.scheduleWithFixedDelay(
- task : variables.task,
+ task : this,
spacedDelay: variables.spacedDelay,
delay : variables.delay,
timeUnit : variables.timeUnit,
- method : variables.method
+ method : "run"
);
- } else if ( variables.period > 0 ) {
+ }
+
+ // Startup a task with a frequency period
+ if ( variables.period > 0 ) {
return variables.executor.scheduleAtFixedRate(
- task : variables.task,
+ task : this,
every : variables.period,
delay : variables.delay,
timeUnit: variables.timeUnit,
- method : variables.method
- );
- } else {
- return variables.executor.schedule(
- task : variables.task,
- delay : variables.delay,
- timeUnit: variables.timeUnit,
- method : variables.method
+ method : "run"
);
}
+
+ // Start off a one-off task
+ return variables.executor.schedule(
+ task : this,
+ delay : variables.delay,
+ timeUnit: variables.timeUnit,
+ method : "run"
+ );
}
+ /**
+ * --------------------------------------------------------------------------
+ * Life - Cycle Methods
+ * --------------------------------------------------------------------------
+ */
+
+ /**
+ * Store the closure to execute before the task is executed
+ *
+ * @target The closure to execute
+ */
+ ScheduledTask function before( required target ){
+ variables.beforeTask = arguments.target;
+ return this;
+ }
+
+ /**
+ * Store the closure to execute after the task is executed
+ *
+ * @target The closure to execute
+ */
+ ScheduledTask function after( required target ){
+ variables.afterTask = arguments.target;
+ return this;
+ }
+
+ /**
+ * Store the closure to execute after the task is executed successfully
+ *
+ * @target The closure to execute
+ */
+ ScheduledTask function onSuccess( required target ){
+ variables.onTaskSuccess = arguments.target;
+ return this;
+ }
+
+ /**
+ * Store the closure to execute after the task is executed successfully
+ *
+ * @target The closure to execute
+ */
+ ScheduledTask function onFailure( required target ){
+ variables.onTaskFailure = arguments.target;
+ return this;
+ }
+
+ /**
+ * --------------------------------------------------------------------------
+ * Frequency Methods
+ * --------------------------------------------------------------------------
+ */
+
/**
* Set a delay in the running of the task that will be registered with this schedule
*
* @delay The delay that will be used before executing the task
* @timeUnit The time unit to use, available units are: days, hours, microseconds, milliseconds, minutes, nanoseconds, and seconds. The default is milliseconds
*/
- Scheduledtask function delay( numeric delay, timeUnit = "milliseconds" ){
+ ScheduledTask function delay( numeric delay, timeUnit = "milliseconds" ){
variables.delay = arguments.delay;
variables.timeUnit = arguments.timeUnit;
return this;
}
/**
- * Set the spaced delay between the executions of this scheduled task
+ * Run the task every custom spaced delay of execution, meaning no overlaps
*
* @delay The delay that will be used before executing the task
* @timeUnit The time unit to use, available units are: days, hours, microseconds, milliseconds, minutes, nanoseconds, and seconds. The default is milliseconds
*/
- Scheduledtask function spacedDelay( numeric spacedDelay, timeUnit = "milliseconds" ){
+ ScheduledTask function spacedDelay( numeric spacedDelay, timeUnit = "milliseconds" ){
variables.spacedDelay = arguments.spacedDelay;
variables.timeUnit = arguments.timeUnit;
return this;
}
/**
- * Set the period of execution for the schedule
+ * Calling this method prevents task frequencies to overlap. By default all tasks are executed with an
+ * interval but ccould potentially overlap if they take longer to execute than the period.
+ *
+ * @period
+ * @timeUnit
+ */
+ ScheduledTask function withNoOverlaps(){
+ variables.noOverlaps = true;
+ return this;
+ }
+
+ /**
+ * Run the task every custom period of execution
*
* @period The period of execution
* @timeUnit The time unit to use, available units are: days, hours, microseconds, milliseconds, minutes, nanoseconds, and seconds. The default is milliseconds
*/
- Scheduledtask function every( numeric period, timeUnit = "milliseconds" ){
+ ScheduledTask function every( numeric period, timeUnit = "milliseconds" ){
variables.period = arguments.period;
variables.timeUnit = arguments.timeUnit;
return this;
}
+ /**
+ * Run the task every minute from the time it get's scheduled
+ */
+ ScheduledTask function everyMinute(){
+ variables.period = 1;
+ variables.timeUnit = "minutes";
+ return this;
+ }
+
+ /**
+ * Run the task every hour from the time it get's scheduled
+ */
+ ScheduledTask function everyHour(){
+ variables.period = 1;
+ variables.timeUnit = "hours";
+ return this;
+ }
+
+ /**
+ * Set the period to be hourly at a specific minute mark and 00 seconds
+ *
+ * @minutes The minutes past the hour mark
+ */
+ ScheduledTask function everyHourAt( required numeric minutes ){
+ var now = getJavaNow();
+ var nextRun = now.withMinute( javacast( "int", arguments.minutes ) ).withSecond( javacast( "int", 0 ) );
+ // If we passed it, then move the hour by 1
+ if ( now.compareTo( nextRun ) > 0 ) {
+ nextRun = nextRun.plusHours( javacast( "int", 1 ) );
+ }
+ // Get the duration time for the next run and delay accordingly
+ this.delay(
+ variables.chronoUnitHelper
+ .duration()
+ .getNative()
+ .between( now, nextRun )
+ .getSeconds(),
+ "seconds"
+ );
+ // Set the period to be every hour
+ variables.period = variables.timeUnitHelper.get( "hours" ).toSeconds( 1 );
+ variables.timeUnit = "seconds";
+
+ return this;
+ }
+
+ /**
+ * Run the task every day at midnight
+ */
+ ScheduledTask function everyDay(){
+ var now = getJavaNow();
+ // Set at midnight
+ var nextRun = now
+ .withHour( javacast( "int", 0 ) )
+ .withMinute( javacast( "int", 0 ) )
+ .withSecond( javacast( "int", 0 ) );
+ // If we passed it, then move to the next day
+ if ( now.compareTo( nextRun ) > 0 ) {
+ nextRun = nextRun.plusDays( javacast( "int", 1 ) );
+ }
+ // Get the duration time for the next run and delay accordingly
+ this.delay(
+ variables.chronoUnitHelper
+ .duration()
+ .getNative()
+ .between( now, nextRun )
+ .getSeconds(),
+ "seconds"
+ );
+ // Set the period to every day in seconds
+ variables.period = variables.timeUnitHelper.get( "days" ).toSeconds( 1 );
+ variables.timeUnit = "seconds";
+
+ return this;
+ }
+
+ /**
+ * Run the task daily with a specific time in 24 hour format: HH:mm
+ * We will always add 0 seconds for you.
+ *
+ * @time The specific time using 24 hour format => HH:mm
+ */
+ ScheduledTask function everyDayAt( required string time ){
+ // Check for mintues else add them
+ if ( !find( ":", arguments.time ) ) {
+ arguments.time &= ":00";
+ }
+ // Validate time format
+ validateTime( arguments.time );
+ // Get times
+ var now = getJavaNow();
+ var nextRun = now
+ .withHour( javacast( "int", getToken( arguments.time, 1, ":" ) ) )
+ .withMinute( javacast( "int", getToken( arguments.time, 2, ":" ) ) )
+ .withSecond( javacast( "int", 0 ) );
+ // If we passed it, then move to the next day
+ if ( now.compareTo( nextRun ) > 0 ) {
+ nextRun = nextRun.plusDays( javacast( "int", 1 ) );
+ }
+ // Get the duration time for the next run and delay accordingly
+ this.delay(
+ variables.chronoUnitHelper
+ .duration()
+ .getNative()
+ .between( now, nextRun )
+ .getSeconds(),
+ "seconds"
+ );
+ // Set the period to every day in seconds
+ variables.period = variables.timeUnitHelper.get( "DAYS" ).toSeconds( 1 );
+ variables.timeUnit = "seconds";
+
+ return this;
+ }
+
+ /**
+ * Run the task every Sunday at midnight
+ */
+ ScheduledTask function everyWeek(){
+ var now = getJavaNow();
+ // Set at midnight
+ var nextRun = now
+ // Sunday
+ .with( variables.chronoUnitHelper.ChronoField.DAY_OF_WEEK, javacast( "int", 7 ) )
+ // Midnight
+ .withHour( javacast( "int", 0 ) )
+ .withMinute( javacast( "int", 0 ) )
+ .withSecond( javacast( "int", 0 ) );
+ // If we passed it, then move to the next week
+ if ( now.compareTo( nextRun ) > 0 ) {
+ nextRun = nextRun.plusWeeks( javacast( "int", 1 ) );
+ }
+ // Get the duration time for the next run and delay accordingly
+ this.delay(
+ variables.chronoUnitHelper
+ .duration()
+ .getNative()
+ .between( now, nextRun )
+ .getSeconds(),
+ "seconds"
+ );
+ // Set the period to every week in seconds
+ variables.period = variables.timeUnitHelper.get( "days" ).toSeconds( 7 );
+ variables.timeUnit = "seconds";
+ variables.dayOfTheWeek = 7;
+ return this;
+ }
+
+ /**
+ * Run the task weekly on the given day of the week and time
+ *
+ * @dayOfWeek The day of the week from 1 (Monday) -> 7 (Sunday)
+ * @time The specific time using 24 hour format => HH:mm, defaults to midnight
+ */
+ ScheduledTask function everyWeekOn( required numeric dayOfWeek, string time = "00:00" ){
+ var now = getJavaNow();
+ // Check for mintues else add them
+ if ( !find( ":", arguments.time ) ) {
+ arguments.time &= ":00";
+ }
+ // Validate time format
+ validateTime( arguments.time );
+ var nextRun = now
+ // Given day
+ .with( variables.chronoUnitHelper.ChronoField.DAY_OF_WEEK, javacast( "int", arguments.dayOfWeek ) )
+ // Given time
+ .withHour( javacast( "int", getToken( arguments.time, 1, ":" ) ) )
+ .withMinute( javacast( "int", getToken( arguments.time, 2, ":" ) ) )
+ .withSecond( javacast( "int", 0 ) );
+ // If we passed it, then move to the next week
+ if ( now.compareTo( nextRun ) > 0 ) {
+ nextRun = nextRun.plusWeeks( javacast( "int", 1 ) );
+ }
+ // Get the duration time for the next run and delay accordingly
+ this.delay(
+ variables.chronoUnitHelper
+ .duration()
+ .getNative()
+ .between( now, nextRun )
+ .getSeconds(),
+ "seconds"
+ );
+ // Set the period to every week in seconds
+ variables.period = variables.timeUnitHelper.get( "days" ).toSeconds( 7 );
+ variables.timeUnit = "seconds";
+ variables.dayOfTheWeek = arguments.dayOfWeek;
+ return this;
+ }
+
+ /**
+ * Run the task on the first day of every month at midnight
+ */
+ ScheduledTask function everyMonth(){
+ var now = getJavaNow();
+ // Set at midnight
+ var nextRun = now
+ // First day of the month
+ .with( variables.chronoUnitHelper.ChronoField.DAY_OF_MONTH, javacast( "int", 1 ) )
+ // Midnight
+ .withHour( javacast( "int", 0 ) )
+ .withMinute( javacast( "int", 0 ) )
+ .withSecond( javacast( "int", 0 ) );
+
+ if ( now.compareTo( nextRun ) > 0 ) {
+ nextRun = nextRun.plusMonths( javacast( "int", 1 ) );
+ }
+ // Get the duration time for the next run and delay accordingly
+ this.delay(
+ variables.chronoUnitHelper
+ .duration()
+ .getNative()
+ .between( now, nextRun )
+ .getSeconds(),
+ "seconds"
+ );
+ // Set the period to one day. And make sure we add a constraint for it
+ // Mostly because every month is different
+ variables.period = variables.timeUnitHelper.get( "days" ).toSeconds( 1 );
+ variables.timeUnit = "seconds";
+ variables.dayOfTheMonth = 1;
+ return this;
+ }
+
+ /**
+ * Run the task every month on a specific day and time
+ *
+ * @day Which day of the month
+ * @time The specific time using 24 hour format => HH:mm, defaults to midnight
+ */
+ ScheduledTask function everyMonthOn( required numeric day, string time = "00:00" ){
+ var now = getJavaNow();
+ // Check for mintues else add them
+ if ( !find( ":", arguments.time ) ) {
+ arguments.time &= ":00";
+ }
+ // Validate time format
+ validateTime( arguments.time );
+ // Get new time
+ var nextRun = now
+ // First day of the month
+ .with( variables.chronoUnitHelper.ChronoField.DAY_OF_MONTH, javacast( "int", arguments.day ) )
+ // Specific Time
+ .withHour( javacast( "int", getToken( arguments.time, 1, ":" ) ) )
+ .withMinute( javacast( "int", getToken( arguments.time, 2, ":" ) ) )
+ .withSecond( javacast( "int", 0 ) );
+ // Have we passed it
+ if ( now.compareTo( nextRun ) > 0 ) {
+ nextRun = nextRun.plusMonths( javacast( "int", 1 ) );
+ }
+ // Get the duration time for the next run and delay accordingly
+ this.delay(
+ variables.chronoUnitHelper
+ .duration()
+ .getNative()
+ .between( now, nextRun )
+ .getSeconds(),
+ "seconds"
+ );
+ // Set the period to one day. And make sure we add a constraint for it
+ // Mostly because every month is different
+ variables.period = variables.timeUnitHelper.get( "days" ).toSeconds( 1 );
+ variables.timeUnit = "seconds";
+ variables.dayOfTheMonth = arguments.day;
+ return this;
+ }
+
+ /**
+ * Run the task on the first Monday of every month
+ *
+ * @time The specific time using 24 hour format => HH:mm, defaults to midnight
+ */
+ ScheduledTask function onFirstBusinessDayOfTheMonth( string time = "00:00" ){
+ var now = getJavaNow();
+ // Check for mintues else add them
+ if ( !find( ":", arguments.time ) ) {
+ arguments.time &= ":00";
+ }
+ // Validate time format
+ validateTime( arguments.time );
+ // Get new time
+ var nextRun = now
+ // First business day of the month
+ .with(
+ createObject( "java", "java.time.temporal.TemporalAdjusters" ).firstInMonth(
+ createObject( "java", "java.time.DayOfWeek" ).MONDAY
+ )
+ )
+ // Specific Time
+ .withHour( javacast( "int", getToken( arguments.time, 1, ":" ) ) )
+ .withMinute( javacast( "int", getToken( arguments.time, 2, ":" ) ) )
+ .withSecond( javacast( "int", 0 ) );
+ // Have we passed it
+ if ( now.compareTo( nextRun ) > 0 ) {
+ nextRun = nextRun.plusMonths( javacast( "int", 1 ) );
+ }
+ // Get the duration time for the next run and delay accordingly
+ this.delay(
+ variables.chronoUnitHelper
+ .duration()
+ .getNative()
+ .between( now, nextRun )
+ .getSeconds(),
+ "seconds"
+ );
+ // Set the period to one day. And make sure we add a constraint for it
+ // Mostly because every month is different
+ variables.period = variables.timeUnitHelper.get( "days" ).toSeconds( 1 );
+ variables.timeUnit = "seconds";
+ variables.dayOfTheMonth = 1;
+ return this;
+ }
+
+ /**
+ * This utility method gives us the last day of the month in Java format
+ */
+ private function getLastDayOfTheMonth(){
+ // Get the last day of the month
+ var lastDay = variables.chronoUnitHelper
+ .toLocalDateTime( now(), getTimezone() )
+ .with( createObject( "java", "java.time.temporal.TemporalAdjusters" ).lastDayOfMonth() );
+ // Verify if on weekend
+ switch ( lastDay.getDayOfWeek().getValue() ) {
+ // Sunday - 2 days
+ case 7: {
+ lastDay = lastDay.minusDays( 2 );
+ break;
+ }
+ // Saturday - 1 day
+ case 6: {
+ lastDay = lastDay.minusDays( 1 );
+ break;
+ }
+ }
+
+ return lastDay;
+ }
+
+ /**
+ * Run the task on the last business day of the month
+ *
+ * @time The specific time using 24 hour format => HH:mm, defaults to midnight
+ */
+ ScheduledTask function onLastBusinessDayOfTheMonth( string time = "00:00" ){
+ var now = getJavaNow();
+ // Check for mintues else add them
+ if ( !find( ":", arguments.time ) ) {
+ arguments.time &= ":00";
+ }
+ // Validate time format
+ validateTime( arguments.time );
+ // Get the last day of the month
+ var nextRun = getLastDayOfTheMonth()
+ // Specific Time
+ .withHour( javacast( "int", getToken( arguments.time, 1, ":" ) ) )
+ .withMinute( javacast( "int", getToken( arguments.time, 2, ":" ) ) )
+ .withSecond( javacast( "int", 0 ) );
+ // Have we passed it
+ if ( now.compareTo( nextRun ) > 0 ) {
+ nextRun = nextRun.plusMonths( javacast( "int", 1 ) );
+ }
+ // Get the duration time for the next run and delay accordingly
+ this.delay(
+ variables.chronoUnitHelper
+ .duration()
+ .getNative()
+ .between( now, nextRun )
+ .getSeconds(),
+ "seconds"
+ );
+ // Set the period to one day. And make sure we add a constraint for it
+ // Mostly because every month is different
+ variables.period = variables.timeUnitHelper.get( "days" ).toSeconds( 1 );
+ variables.timeUnit = "seconds";
+ variables.lastBusinessDay = true;
+ return this;
+ }
+
+ /**
+ * Run the task on the first day of the year at midnight
+ */
+ ScheduledTask function everyYear(){
+ var now = getJavaNow();
+ // Set at midnight
+ var nextRun = now
+ // First day of the month
+ .with( variables.chronoUnitHelper.ChronoField.DAY_OF_YEAR, javacast( "int", 1 ) )
+ // Midnight
+ .withHour( javacast( "int", 0 ) )
+ .withMinute( javacast( "int", 0 ) )
+ .withSecond( javacast( "int", 0 ) );
+
+ if ( now.compareTo( nextRun ) > 0 ) {
+ nextRun = nextRun.plusYears( javacast( "int", 1 ) );
+ }
+ // Get the duration time for the next run and delay accordingly
+ this.delay(
+ variables.chronoUnitHelper
+ .duration()
+ .getNative()
+ .between( now, nextRun )
+ .getSeconds(),
+ "seconds"
+ );
+ // Set the period to
+ variables.period = variables.timeUnitHelper.get( "days" ).toSeconds( 365 );
+ variables.timeUnit = "seconds";
+ return this;
+ }
+
+ /**
+ * Set the period to be weekly at a specific time at a specific day of the week
+ *
+ * @month The month in numeric format 1-12
+ * @day Which day of the month
+ * @time The specific time using 24 hour format => HH:mm, defaults to 00:00
+ */
+ ScheduledTask function everyYearOn(
+ required numeric month,
+ required numeric day,
+ required string time = "00:00"
+ ){
+ var now = getJavaNow();
+ // Check for mintues else add them
+ if ( !find( ":", arguments.time ) ) {
+ arguments.time &= ":00";
+ }
+ // Validate time format
+ validateTime( arguments.time );
+ var nextRun = now
+ // Specific month
+ .with( variables.chronoUnitHelper.ChronoField.MONTH_OF_YEAR, javacast( "int", arguments.month ) )
+ // Specific day of the month
+ .with( variables.chronoUnitHelper.ChronoField.DAY_OF_MONTH, javacast( "int", arguments.day ) )
+ // Midnight
+ .withHour( javacast( "int", getToken( arguments.time, 1, ":" ) ) )
+ .withMinute( javacast( "int", getToken( arguments.time, 2, ":" ) ) )
+ .withSecond( javacast( "int", 0 ) );
+ // Have we passed it?
+ if ( now.compareTo( nextRun ) > 0 ) {
+ nextRun = nextRun.plusYears( javacast( "int", 1 ) );
+ }
+ // Get the duration time for the next run and delay accordingly
+ this.delay(
+ variables.chronoUnitHelper
+ .duration()
+ .getNative()
+ .between( now, nextRun )
+ .getSeconds(),
+ "seconds"
+ );
+ // Set the period to
+ variables.period = variables.timeUnitHelper.get( "days" ).toSeconds( 365 );
+ variables.timeUnit = "seconds";
+ return this;
+ }
+
+ /**
+ * Run the task on saturday and sundays
+ *
+ * @time The specific time using 24 hour format => HH:mm, defaults to 00:00
+ */
+ ScheduledTask function onWeekends( string time = "00:00" ){
+ // Check for mintues else add them
+ if ( !find( ":", arguments.time ) ) {
+ arguments.time &= ":00";
+ }
+ // Validate time format
+ validateTime( arguments.time );
+ // Get times
+ var now = getJavaNow();
+ var nextRun = now
+ .withHour( javacast( "int", getToken( arguments.time, 1, ":" ) ) )
+ .withMinute( javacast( "int", getToken( arguments.time, 2, ":" ) ) )
+ .withSecond( javacast( "int", 0 ) );
+ // If we passed it, then move to the next day
+ if ( now.compareTo( nextRun ) > 0 ) {
+ nextRun = nextRun.plusDays( javacast( "int", 1 ) );
+ }
+ // Get the duration time for the next run and delay accordingly
+ this.delay(
+ variables.chronoUnitHelper
+ .duration()
+ .getNative()
+ .between( now, nextRun )
+ .getSeconds(),
+ "seconds"
+ );
+ // Set the period to every day in seconds
+ variables.period = variables.timeUnitHelper.get( "DAYS" ).toSeconds( 1 );
+ variables.timeUnit = "seconds";
+ // Constraint to only run on weekends
+ variables.weekends = true;
+ variables.weekdays = false;
+
+ return this;
+ }
+
+ /**
+ * Set the period to be from Monday - Friday
+ *
+ * @time The specific time using 24 hour format => HH:mm, defaults to 00:00
+ */
+ ScheduledTask function onWeekdays( string time = "00:00" ){
+ // Check for mintues else add them
+ if ( !find( ":", arguments.time ) ) {
+ arguments.time &= ":00";
+ }
+ // Validate time format
+ validateTime( arguments.time );
+ // Get times
+ var now = getJavaNow();
+ var nextRun = now
+ .withHour( javacast( "int", getToken( arguments.time, 1, ":" ) ) )
+ .withMinute( javacast( "int", getToken( arguments.time, 2, ":" ) ) )
+ .withSecond( javacast( "int", 0 ) );
+ // If we passed it, then move to the next day
+ if ( now.compareTo( nextRun ) > 0 ) {
+ nextRun = nextRun.plusDays( javacast( "int", 1 ) );
+ }
+ // Get the duration time for the next run and delay accordingly
+ this.delay(
+ variables.chronoUnitHelper
+ .duration()
+ .getNative()
+ .between( now, nextRun )
+ .getSeconds(),
+ "seconds"
+ );
+ // Set the period to every day in seconds
+ variables.period = variables.timeUnitHelper.get( "DAYS" ).toSeconds( 1 );
+ variables.timeUnit = "seconds";
+ // Constraint to only run on weekdays
+ variables.weekdays = true;
+ variables.weekends = false;
+ return this;
+ }
+
+ /**
+ * Set the period to be on Mondays
+ *
+ * @time The specific time using 24 hour format => HH:mm, defaults to 00:00
+ */
+ ScheduledTask function onMondays( string time = "00:00" ){
+ return this.everyWeekOn( 1, arguments.time );
+ }
+
+ /**
+ * Set the period to be on Tuesdays
+ *
+ * @time The specific time using 24 hour format => HH:mm, defaults to 00:00
+ */
+ ScheduledTask function onTuesdays( string time = "00:00" ){
+ return this.everyWeekOn( 2, arguments.time );
+ }
+
+ /**
+ * Set the period to be on Wednesdays
+ *
+ * @time The specific time using 24 hour format => HH:mm, defaults to 00:00
+ */
+ ScheduledTask function onWednesdays( string time = "00:00" ){
+ return this.everyWeekOn( 3, arguments.time );
+ }
+
+ /**
+ * Set the period to be on Thursdays
+ *
+ * @time The specific time using 24 hour format => HH:mm, defaults to 00:00
+ */
+ ScheduledTask function onThursdays( string time = "00:00" ){
+ return this.everyWeekOn( 4, arguments.time );
+ }
+
+ /**
+ * Set the period to be on Fridays
+ *
+ * @time The specific time using 24 hour format => HH:mm, defaults to 00:00
+ */
+ ScheduledTask function onFridays( string time = "00:00" ){
+ return this.everyWeekOn( 5, arguments.time );
+ }
+
+ /**
+ * Set the period to be on Saturdays
+ *
+ * @time The specific time using 24 hour format => HH:mm, defaults to 00:00
+ */
+ ScheduledTask function onSaturdays( string time = "00:00" ){
+ return this.everyWeekOn( 6, arguments.time );
+ }
+
+ /**
+ * Set the period to be on Sundays
+ *
+ * @time The specific time using 24 hour format => HH:mm, defaults to 00:00
+ */
+ ScheduledTask function onSundays( string time = "00:00" ){
+ return this.everyWeekOn( 7, arguments.time );
+ }
+
+ /**
+ * --------------------------------------------------------------------------
+ * TimeUnit Methods
+ * --------------------------------------------------------------------------
+ */
+
/**
* Set the time unit in days
*/
- Scheduledtask function inDays(){
+ ScheduledTask function inDays(){
variables.timeUnit = "days";
return this;
}
@@ -123,7 +1213,7 @@ component accessors="true" {
/**
* Set the time unit in hours
*/
- Scheduledtask function inHours(){
+ ScheduledTask function inHours(){
variables.timeUnit = "hours";
return this;
}
@@ -131,7 +1221,7 @@ component accessors="true" {
/**
* Set the time unit in microseconds
*/
- Scheduledtask function inMicroseconds(){
+ ScheduledTask function inMicroseconds(){
variables.timeUnit = "microseconds";
return this;
}
@@ -139,7 +1229,7 @@ component accessors="true" {
/**
* Set the time unit in milliseconds
*/
- Scheduledtask function inMilliseconds(){
+ ScheduledTask function inMilliseconds(){
variables.timeUnit = "milliseconds";
return this;
}
@@ -147,7 +1237,7 @@ component accessors="true" {
/**
* Set the time unit in minutes
*/
- Scheduledtask function inMinutes(){
+ ScheduledTask function inMinutes(){
variables.timeUnit = "minutes";
return this;
}
@@ -155,7 +1245,7 @@ component accessors="true" {
/**
* Set the time unit in nanoseconds
*/
- Scheduledtask function inNanoseconds(){
+ ScheduledTask function inNanoseconds(){
variables.timeUnit = "nanoseconds";
return this;
}
@@ -163,9 +1253,33 @@ component accessors="true" {
/**
* Set the time unit in seconds
*/
- Scheduledtask function inSeconds(){
+ ScheduledTask function inSeconds(){
variables.timeUnit = "seconds";
return this;
}
+ /**
+ * Validates an incoming string to adhere to either: HH:mm
+ *
+ * @time The time to check
+ *
+ * @throws InvalidTimeException - If the time is invalid, else it just continues operation
+ */
+ private function validateTime( required time ){
+ // Regex check
+ if ( !reFind( "^[0-2][0-9]\:[0-5][0-9]$", arguments.time ) ) {
+ throw(
+ message = "Invalid time representation (#arguments.time#). Time is represented in 24 hour minute format => HH:mm",
+ type = "InvalidTimeException"
+ );
+ }
+ }
+
+ /**
+ * Get a Java localDateTime object using the current date/time and timezone
+ */
+ function getJavaNow(){
+ return variables.chronoUnitHelper.toLocalDateTime( now(), getTimezone() );
+ }
+
}
diff --git a/system/async/tasks/Scheduler.cfc b/system/async/tasks/Scheduler.cfc
new file mode 100644
index 000000000..7ee1f8f5e
--- /dev/null
+++ b/system/async/tasks/Scheduler.cfc
@@ -0,0 +1,347 @@
+/**
+ * The Async Scheduler is in charge of registering scheduled tasks, starting them, monitoring them and shutting them down if needed.
+ *
+ * Each scheduler is bound to an scheduled executor class. You can override the executor using the `setExecutor()` method if you so desire.
+ * The scheduled executor will be named {name}-scheduler
+ *
+ * In a ColdBox context, you might have the global scheduler in charge of the global tasks and also 1 per module as well in HMVC fashion.
+ * In a ColdBox context, this object will inherit from the ColdBox super type as well dynamically at runtime.
+ *
+ */
+component accessors="true" singleton {
+
+ /**
+ * --------------------------------------------------------------------------
+ * Properties
+ * --------------------------------------------------------------------------
+ */
+
+ /**
+ * The name of this scheduler
+ */
+ property name="name";
+
+ /**
+ * An ordered struct of all the tasks this scheduler manages
+ */
+ property name="tasks" type="struct";
+
+ /**
+ * The Scheduled Executor we are bound to
+ */
+ property name="executor";
+
+ /**
+ * The default timezone to use for task executions
+ */
+ property name="timezone";
+
+ /**
+ * Constructor
+ *
+ * @name The name of this scheduler
+ * @asyncManager The async manager we are linked to
+ */
+ function init( required name, required asyncManager ){
+ // Utility class
+ variables.util = new coldbox.system.core.util.Util();
+ // Name
+ variables.name = arguments.name;
+ // The async manager
+ variables.asyncManager = arguments.asyncManager;
+ // The collection of tasks we will run
+ variables.tasks = structNew( "ordered" );
+ // Default TimeZone to UTC for all tasks
+ variables.timezone = createObject( "java", "java.time.ZoneId" ).systemDefault();
+ // Build out the executor for this scheduler
+ variables.executor = arguments.asyncManager.newExecutor(
+ name: arguments.name & "-scheduler",
+ type: "scheduled"
+ );
+ // Bit that denotes if this scheduler has been started or not
+ variables.started = false;
+ // Send notice
+ arguments.asyncManager.out( "√ Scheduler (#arguments.name#) has been registered" );
+
+ return this;
+ }
+
+ /**
+ * Usually where concrete implementations add their tasks and configs
+ */
+ function configure(){
+ }
+
+ /**
+ * Set the timezone for all tasks to use using the timezone string identifier
+ *
+ * @see https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/ZoneId.html
+ *
+ * @timezone The timezone string identifier
+ */
+ Scheduler function setTimezone( required timezone ){
+ variables.timezone = createObject( "java", "java.time.ZoneId" ).of( arguments.timezone );
+ return this;
+ }
+
+ /**
+ * Register a new task in this scheduler that will be executed once the `startup()` is fired or manually
+ * via the run() method of the task.
+ *
+ * @return a ScheduledTask object so you can work on the registration of the task
+ */
+ ScheduledTask function task( required name ){
+ // Create task with custom name
+ var oTask = variables.executor
+ // Give me the task broda!
+ .newTask( arguments.name )
+ // Register ourselves in the task
+ .setScheduler( this )
+ // Set default timezone into the task
+ .setTimezone( getTimezone().getId() );
+
+ // Register the task by name
+ variables.tasks[ arguments.name ] = {
+ // task name
+ "name" : arguments.name,
+ // task object
+ "task" : oTask,
+ // task scheduled future object
+ "future" : "",
+ // when it registers
+ "registeredAt" : now(),
+ // when it's scheduled
+ "scheduledAt" : "",
+ // Tracks if the task has been disabled for startup purposes
+ "disabled" : false,
+ // If there is an error scheduling the task
+ "error" : false,
+ // Any error messages when scheduling
+ "errorMessage" : "",
+ // The exception stacktrace if something went wrong scheduling the task
+ "stacktrace" : "",
+ // Server Host
+ "inetHost" : variables.util.discoverInetHost(),
+ // Server IP
+ "localIp" : variables.util.getServerIp()
+ };
+
+ return oTask;
+ }
+
+ /**
+ * --------------------------------------------------------------------------
+ * Startup/Shutdown Methods
+ * --------------------------------------------------------------------------
+ */
+
+ /**
+ * Startup this scheduler and all of it's scheduled tasks
+ */
+ Scheduler function startup(){
+ if ( !variables.started ) {
+ lock name="scheduler-#getName()#-startup" type="exclusive" timeout="45" throwOnTimeout="true" {
+ if ( !variables.started ) {
+ // Iterate over tasks and send them off for scheduling
+ variables.tasks.each( function( taskName, taskRecord ){
+ // Verify we can start it up the task or not
+ if ( arguments.taskRecord.task.isDisabled() ) {
+ arguments.taskRecord.disabled = true;
+ variables.asyncManager.out(
+ "- Scheduler (#getName()#) skipping task (#arguments.taskRecord.task.getName()#) as it is disabled."
+ );
+ // Continue iteration
+ return;
+ } else {
+ // Log scheduling startup
+ variables.asyncManager.out(
+ "- Scheduler (#getName()#) scheduling task (#arguments.taskRecord.task.getName()#)..."
+ );
+ }
+
+ // Send it off for scheduling
+ try {
+ arguments.taskRecord.future = arguments.taskRecord.task.start();
+ arguments.taskRecord.scheduledAt = now();
+ variables.asyncManager.out(
+ "√ Task (#arguments.taskRecord.task.getName()#) scheduled successfully."
+ );
+ } catch ( any e ) {
+ variables.asyncManager.err(
+ "X Error scheduling task (#arguments.taskRecord.task.getName()#) => #e.message# #e.detail#"
+ );
+ arguments.taskRecord.error = true;
+ arguments.taskRecord.errorMessage = e.message & e.detail;
+ arguments.taskRecord.stackTrace = e.stacktrace;
+ }
+ } );
+
+ // Mark scheduler as started
+ variables.started = true;
+
+ // callback
+ this.onStartup();
+
+ // Log it
+ variables.asyncManager.out( "√ Scheduler (#getname()#) has started!" );
+ }
+ // end double if not started
+ }
+ // end lock
+ }
+ // end if not started
+
+ return this;
+ }
+
+ /**
+ * Check if this scheduler has started or not
+ */
+ boolean function hasStarted(){
+ return variables.started;
+ }
+
+ /**
+ * Shutdown this scheduler by calling the executor to shutdown and disabling all tasks
+ */
+ Scheduler function shutdown(){
+ // callback
+ this.onShutdown();
+ // shutdown executor
+ variables.executor.shutdownNow();
+ // Mark it
+ variables.started = false;
+ // Log it
+ variables.asyncManager.out( "√ Scheduler (#getName()#) has been shutdown!" );
+ return this;
+ }
+
+ /**
+ * --------------------------------------------------------------------------
+ * Life - Cycle Callbacks
+ * --------------------------------------------------------------------------
+ */
+
+ /**
+ * Called before the scheduler is going to be shutdown
+ */
+ function onShutdown(){
+ }
+
+ /**
+ * Called after the scheduler has registered all schedules
+ */
+ function onStartup(){
+ }
+
+ /**
+ * Called whenever ANY task fails
+ *
+ * @task The task that got executed
+ * @exception The ColdFusion exception object
+ *
+ */
+ function onAnyTaskError( required task, required exception ){
+ }
+
+ /**
+ * Called whenever ANY task succeeds
+ *
+ * @task The task that got executed
+ * @result The result (if any) that the task produced
+ *
+ */
+ function onAnyTaskSuccess( required task, result ){
+ }
+
+ /**
+ * Called before ANY task runs
+ *
+ * @task The task about to be executed
+ *
+ */
+ function beforeAnyTask( required task ){
+ }
+
+ /**
+ * Called after ANY task runs
+ *
+ * @task The task that got executed
+ * @result The result (if any) that the task produced
+ *
+ */
+ function afterAnyTask( required task, result ){
+ }
+
+ /**
+ * --------------------------------------------------------------------------
+ * Utility Methods
+ * --------------------------------------------------------------------------
+ */
+
+ /**
+ * Builds out a report for all the registered tasks in this scheduler
+ */
+ struct function getTaskStats(){
+ // return back a struct of stats for each registered task
+ return variables.tasks.map( function( key, record ){
+ return arguments.record.task.getStats();
+ } );
+ }
+
+ /**
+ * Get an array of all the tasks managed by this scheduler
+ */
+ array function getRegisteredTasks(){
+ var taskKeys = variables.tasks.keyArray();
+ taskKeys.sort( "textnocase" );
+ return taskKeys;
+ }
+
+ /**
+ * Checks if this scheduler manages a task by name
+ *
+ * @name The name of the task to search
+ */
+ boolean function hasTask( required name ){
+ return variables.tasks.keyExists( arguments.name );
+ }
+
+ /**
+ * Get's a task record from the collection by name
+ *
+ * @name The name of the task
+ *
+ * @throws UnregisteredTaskException if no task is found under that name
+ *
+ * @return The task record struct: { name, task, future, scheduledAt, registeredAt, error, errorMessage, stacktrace }
+ */
+ struct function getTaskRecord( required name ){
+ if ( hasTask( arguments.name ) ) {
+ return variables.tasks[ arguments.name ];
+ }
+ throw( type: "UnregisteredTaskException", message: "No task found with the name: #arguments.name#" );
+ }
+
+ /**
+ * Unregister a task from this scheduler
+ *
+ * @name The name of the task to remove
+ *
+ * @throws UnregisteredTaskException if no task is found under that name
+ */
+ Scheduler function removeTask( required name ){
+ // Remove from executor if registered
+ var taskRecord = getTaskRecord( arguments.name );
+
+ // Check if the task has been registered so we can cancel it
+ if ( isObject( taskRecord.future ) ) {
+ taskRecord.future.cancel( mayInterruptIfRunning = true );
+ }
+
+ // Delete it
+ variables.tasks.delete( arguments.name );
+ return this;
+ }
+
+}
diff --git a/system/async/time/ChronoUnit.cfc b/system/async/time/ChronoUnit.cfc
new file mode 100644
index 000000000..2aec386d3
--- /dev/null
+++ b/system/async/time/ChronoUnit.cfc
@@ -0,0 +1,166 @@
+/**
+ * We represent a chrono unit class that assists with time units on date/time conversions
+ * It doesn't hold any date/time information.
+ *
+ * @see https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/temporal/ChronoUnit.html
+ * @see https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/ZoneId.html
+ * @see https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/ZoneOffset.html
+ */
+component singleton {
+
+ // TimeZone Helpers
+ this.ZoneOffset = createObject( "java", "java.time.ZoneOffset" );
+ this.ZoneId = createObject( "java", "java.time.ZoneId" );
+ this.ChronoField = createObject( "java", "java.time.temporal.ChronoField" );
+
+ // The java chrono unit we model
+ variables.jChronoUnit = createObject( "java", "java.time.temporal.ChronoUnit" );
+ // Unit that represents the concept of a century.
+ this.CENTURIES = variables.jChronoUnit.CENTURIES;
+ // Unit that represents the concept of a day.
+ this.DAYS = variables.jChronoUnit.DAYS;
+ // Unit that represents the concept of a decade.
+ this.DECADES = variables.jChronoUnit.DECADES;
+ // Unit that represents the concept of an era.
+ this.ERAS = variables.jChronoUnit.ERAS;
+ // Artificial unit that represents the concept of forever.
+ this.FOREVER = variables.jChronoUnit.FOREVER;
+ // Unit that represents the concept of half a day, as used in AM/PM.
+ this.HALF_DAYS = variables.jChronoUnit.HALF_DAYS;
+ // Unit that represents the concept of an hour.
+ this.HOURS = variables.jChronoUnit.HOURS;
+ // Unit that represents the concept of a microsecond.
+ this.MICROS = variables.jChronoUnit.MICROS;
+ // Unit that represents the concept of a millennium.
+ this.MILLENNIA = variables.jChronoUnit.MILLENNIA;
+ // Unit that represents the concept of a millisecond.
+ this.MILLIS = variables.jChronoUnit.MILLIS;
+ // Unit that represents the concept of a minute.
+ this.MINUTES = variables.jChronoUnit.MINUTES;
+ // Unit that represents the concept of a month.
+ this.MONTHS = variables.jChronoUnit.MONTHS;
+ // Unit that represents the concept of a nanosecond, the smallest supported unit of time.
+ this.NANOS = variables.jChronoUnit.NANOS;
+ // Unit that represents the concept of a second.
+ this.SECONDS = variables.jChronoUnit.SECONDS;
+ // Unit that represents the concept of a week.
+ this.WEEKS = variables.jChronoUnit.WEEKS;
+ // Unit that represents the concept of a year.
+ this.YEARS = variables.jChronoUnit.YEARS;
+
+ /**
+ * Convert any ColdFusion date/time or string date/time object to a Java instant temporal object
+ *
+ * @target The date/time or string object representing the date/time
+ *
+ * @return A Java temporal object as java.time.Instant
+ */
+ function toInstant( required target ){
+ // Is this a date/time object or a string?
+ if ( findNoCase( "string", arguments.target.getClass().getName() ) ) {
+ arguments.target = createODBCDateTime( arguments.target );
+ }
+ return arguments.target.toInstant();
+ }
+
+ /**
+ * Convert any ColdFUsion date/time or string date/time object to the new Java.time.LocalDateTime class so we can use them as Temporal objects
+ *
+ * @target The cf date/time or string object representing the date/time
+ * @timezone If passed, we will use this timezone to build the temporal object. Else we default to UTC
+ *
+ * @throws DateTimeException - if the zone ID has an invalid format
+ * @throws ZoneRulesException - if the zone ID is a region ID that cannot be found
+ *
+ * @return A Java temporal object as java.time.LocalDateTime
+ */
+ function toLocalDateTime( required target, timezone ){
+ return this
+ .toInstant( arguments.target )
+ .atZone(
+ isNull( arguments.timezone ) ? this.ZoneOffset.UTC : this.ZoneId.of(
+ javacast( "string", arguments.timezone )
+ )
+ )
+ .toLocalDateTime();
+ }
+
+ /**
+ * Convert any ColdFUsion date/time or string date/time object to the new Java.time.LocalDate class so we can use them as Temporal objects
+ *
+ * @target The cf date/time or string object representing the date/time
+ * @timezone If passed, we will use this timezone to build the temporal object. Else we default to UTC
+ *
+ * @throws DateTimeException - if the zone ID has an invalid format
+ * @throws ZoneRulesException - if the zone ID is a region ID that cannot be found
+ *
+ * @return A Java temporal object as java.time.LocalDate
+ */
+ function toLocalDate( required target, timezone ){
+ return this
+ .toInstant( arguments.target )
+ .atZone(
+ isNull( arguments.timezone ) ? this.ZoneOffset.UTC : this.ZoneId.of(
+ javacast( "string", arguments.timezone )
+ )
+ )
+ .toLocalDate();
+ }
+
+ /**
+ * Get the Java Zone ID of the passed in timezone identifier string
+ *
+ * @see https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/ZoneId.html
+ *
+ * @timezone The String timezone identifier
+ *
+ * @throws DateTimeException - if the zone ID has an invalid format
+ * @throws ZoneRulesException - if the zone ID is a region ID that cannot be found
+ *
+ * @return Java Timezone java.time.ZoneId
+ */
+ function getTimezone( required timezone ){
+ return this.ZoneId.of( javacast( "string", arguments.timezone ) );
+ }
+
+ /**
+ * This queries TimeZone.getDefault() to find the default time-zone and converts it to a ZoneId. If the system default time-zone is changed, then the result of this method will also change.
+ *
+ * @see https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/ZoneId.html
+ *
+ * @return Java Timezone java.time.ZoneId
+ */
+ function getSystemTimezone(){
+ return this.ZoneId.systemDefault();
+ }
+
+ /**
+ * Convert any date/time or string date/time object to a Java Date/Time
+ *
+ * @target The date/time or string object representing the date/time
+ *
+ * @return A java date time object
+ */
+ function toJavaDate( required target ){
+ // Is this a date/time object or a string?
+ if ( findNoCase( "string", arguments.target.getClass().getName() ) ) {
+ arguments.target = createODBCDateTime( arguments.target );
+ }
+ return arguments.target;
+ }
+
+ /**
+ * Build out a new Duration class
+ */
+ Duration function duration(){
+ return new coldbox.system.async.time.Duration( argumentCollection = arguments );
+ }
+
+ /**
+ * Build out a new Period class
+ */
+ Period function period(){
+ return new coldbox.system.async.time.Period( argumentCollection = arguments );
+ }
+
+}
diff --git a/system/async/time/Duration.cfc b/system/async/time/Duration.cfc
new file mode 100644
index 000000000..d032368c9
--- /dev/null
+++ b/system/async/time/Duration.cfc
@@ -0,0 +1,510 @@
+/**
+ * A time-based amount of time, such as '34.5 seconds'.
+ *
+ * This class models a quantity or amount of time in terms of seconds and nanoseconds.
+ * It can be accessed using other duration-based units, such as minutes and hours.
+ * In addition, the DAYS unit can be used and is treated as exactly equal to 24 hours, thus ignoring daylight savings effects.
+ * See Period for the date-based equivalent to this class.
+ *
+ * Static class to map to the Java JDK Duration static class with some CF Goodness
+ *
+ * @see https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/Duration.html
+ */
+component accessors="true" {
+
+ // The static java class we represent
+ variables.jDuration = createObject( "java", "java.time.Duration" );
+ // A standard set of date periods units.
+ this.CHRONO_UNIT = new ChronoUnit();
+
+ /**
+ * Initialize to zero
+ */
+ function init(){
+ return this.of( 0 );
+ }
+
+ /**
+ * --------------------------------------------------------------------------
+ * Utility Methods
+ * --------------------------------------------------------------------------
+ */
+
+ /**
+ * Get the native java class we proxy to.
+ *
+ * @return java.time.Duration
+ */
+ any function getNative(){
+ return variables.jDuration;
+ }
+
+ /**
+ * Returns a copy of this duration with a positive length.
+ */
+ Duration function abs(){
+ variables.jDuration = variables.jDuration.abs();
+ return this;
+ }
+
+ /**
+ * Checks if the duration is negative, excluding zero
+ */
+ boolean function isNegative(){
+ return variables.jDuration.isNegative();
+ }
+
+ /**
+ * Checks if the duration is zero length
+ */
+ boolean function isZero(){
+ return variables.jDuration.isZero();
+ }
+
+ /**
+ * Checks if this duration is equal to the specified Duration.
+ *
+ * @otherDuration The duration to compare against
+ */
+ boolean function isEquals( required Duration otherDuration ){
+ return variables.jDuration.equals( arguments.otherDuration.getNative() );
+ }
+
+ /**
+ * Returns a copy of this duration with the length negated.
+ */
+ Duration function negated(){
+ variables.jDuration = variables.jDuration.negated();
+ return this;
+ }
+
+ /**
+ * --------------------------------------------------------------------------
+ * Retrieval Methods
+ * --------------------------------------------------------------------------
+ */
+
+ /**
+ * Gets the value of the requested unit in seconds by default or by nanoseconds
+ *
+ * @unit Seconds or nano
+ *
+ * @throws UnsupportedTemporalTypeException This returns a value for each of the two supported units, SECONDS and NANOS. All other units throw an exception.
+ */
+ numeric function get( unit = "seconds" ){
+ return variables.jDuration.get( this.CHRONO_UNIT[ arguments.unit ] );
+ }
+
+ /**
+ * Gets the number of seconds in this duration.
+ */
+ numeric function getSeconds(){
+ return this.get();
+ }
+
+ /**
+ * Gets the number of nano seconds in this duration.
+ */
+ numeric function getNano(){
+ return this.get( "nanos" );
+ }
+
+ /**
+ * Gets the set (array) of units supported by this duration.
+ */
+ array function getUnits(){
+ return arrayMap( variables.jDuration.getUnits(), function( thisItem ){
+ return arguments.thisItem.name();
+ } );
+ }
+
+ /**
+ * Gets the number of days in this duration.
+ */
+ numeric function toDays(){
+ return variables.jDuration.toDays();
+ }
+
+ /**
+ * Extracts the number of days in the duration.
+ */
+ numeric function toDaysPart(){
+ return variables.jDuration.toDaysPart();
+ }
+
+ /**
+ * Gets the number of Hours in this duration.
+ */
+ numeric function toHours(){
+ return variables.jDuration.toHours();
+ }
+
+ /**
+ * Extracts the number of HoursPart in this duration.
+ */
+ numeric function toHoursPart(){
+ return variables.jDuration.toHoursPart();
+ }
+
+ /**
+ * Gets the number of Millis in this duration.
+ */
+ numeric function toMillis(){
+ return variables.jDuration.toMillis();
+ }
+
+ /**
+ * Extracts the number of MillisPart in this duration.
+ */
+ numeric function toMillisPart(){
+ return variables.jDuration.toMillisPart();
+ }
+
+ /**
+ * Gets the number of Minutes in this duration.
+ */
+ numeric function toMinutes(){
+ return variables.jDuration.toMinutes();
+ }
+
+ /**
+ * Extracts the number of MinutesPart in this duration.
+ */
+ numeric function toMinutesPart(){
+ return variables.jDuration.toMinutesPart();
+ }
+
+ /**
+ * Gets the number of Nanos in this duration.
+ */
+ numeric function toNanos(){
+ return variables.jDuration.toNanos();
+ }
+
+ /**
+ * Extracts the number of NanosPart in this duration.
+ */
+ numeric function toNanosPart(){
+ return variables.jDuration.toNanosPart();
+ }
+
+ /**
+ * Gets the number of Seconds in this duration.
+ */
+ numeric function toSeconds(){
+ return variables.jDuration.toSeconds();
+ }
+
+ /**
+ * Extracts the number of SecondsPart in this duration.
+ */
+ numeric function toSecondsPart(){
+ return variables.jDuration.toSecondsPart();
+ }
+
+ /**
+ * A string representation of this duration using ISO-8601 seconds based representation, such as PT8H6M12.345S.
+ */
+ string function toString(){
+ return variables.jDuration.toString();
+ }
+
+ /**
+ * --------------------------------------------------------------------------
+ * Operations Methods
+ * --------------------------------------------------------------------------
+ */
+
+ /**
+ * Adds this duration to the specified temporal object and return back to you a date/time object
+ *
+ * @target The date/time object or string to incorporate the duration into
+ * @asInstant Return the result either as a date/time string or a java.time.Instant object
+ *
+ * @return Return the result either as a date/time string or a java.time.Instant object
+ */
+ function addTo( required target, boolean asInstant = false ){
+ var results = variables.jDuration.addTo( this.CHRONO_UNIT.toInstant( arguments.target ) );
+ return ( arguments.asInstant ? results : results.toString() );
+ }
+
+ /**
+ * Subtracts this duration to the specified temporal object and return back to you a date/time object
+ *
+ * @target The date/time object or string to subtract the duration from
+ * @asInstant Return the result either as a date/time string or a java.time.Instant object
+ *
+ * @return Return the result either as a date/time string or a java.time.Instant object
+ */
+ function subtractFrom( required target, boolean asInstant = false ){
+ var results = variables.jDuration.subtractFrom( this.CHRONO_UNIT.toInstant( arguments.target ) );
+ return ( arguments.asInstant ? results : results.toString() );
+ }
+
+ /**
+ * Obtains a Duration representing the duration between two temporal date objects.
+ *
+ * This calculates the duration between two temporal objects. If the objects are of different types,
+ * then the duration is calculated based on the type of the first object. For example, if the first argument is a
+ * LocalTime then the second argument is converted to a LocalTime.
+ *
+ * @start The start date/time object
+ * @end The end date/time object
+ */
+ Duration function between( required start, required end ){
+ // Do it!
+ variables.jDuration = variables.jDuration.between(
+ this.CHRONO_UNIT.toInstant( arguments.start ),
+ this.CHRONO_UNIT.toInstant( arguments.end )
+ );
+ return this;
+ }
+
+ /**
+ * Compares this duration to the specified Duration.
+ *
+ * The comparison is based on the total length of the durations. It is "consistent with equals", as defined by Comparable.
+ *
+ * @otherDuration the other duration to compare to
+ *
+ * @return -1 if the duration is less than the otherDuration, 0 if equals, and 1 if greater than
+ */
+ function compareTo( required Duration otherDuration ){
+ return variables.jDuration.compareTo( arguments.otherDuration.getNative() );
+ }
+
+ /**
+ * Returns a copy of this duration divided by the specified value.
+ *
+ * @divisor Divide by what?
+ */
+ Duration function dividedBy( required divisor ){
+ variables.jDuration = variables.jDuration.dividedBy( javacast( "long", arguments.divisor ) );
+ return this;
+ }
+
+ /**
+ * Returns a copy of this duration with the specified duration subtracted.
+ *
+ * @amountToSubtract The amount to subtract
+ * @unit The units to use
+ */
+ Duration function minus( required amountToSubtract, unit = "seconds" ){
+ variables.jDuration = variables.jDuration.minus(
+ javacast( "long", arguments.amountToSubtract ),
+ this.CHRONO_UNIT[ arguments.unit ]
+ );
+ return this;
+ }
+
+ Duration function minusDays( required daysToSubtract ){
+ return this.minus( arguments.daysToSubtract, "days" );
+ }
+
+ Duration function minusHours( required hoursToSubtract ){
+ return this.minus( arguments.hoursToSubtract, "hours" );
+ }
+
+ Duration function minusMillis( required millisToSubtract ){
+ return this.minus( arguments.millisToSubtract, "millis" );
+ }
+
+ Duration function minusMinutes( required minutesToSubtract ){
+ return this.minus( arguments.minutesToSubtract, "minutes" );
+ }
+
+ Duration function minusNanos( required nanosToSubtract ){
+ return this.minus( arguments.nanosToSubtract, "nanos" );
+ }
+
+ Duration function minusSeconds( required secondsToSubtract ){
+ return this.minus( arguments.secondsToSubtract, "seconds" );
+ }
+
+ Duration function multipliedBy( required multiplicand ){
+ variables.jDuration = variables.jDuration.multipliedBy( javacast( "long", arguments.multiplicand ) );
+ return this;
+ }
+
+ /**
+ * Returns a copy of this duration with the specified duration added.
+ *
+ * @amountToAdd The amount to add
+ * @unit The units to use
+ */
+ Duration function plus( required amountToAdd, unit = "seconds" ){
+ variables.jDuration = variables.jDuration.plus(
+ javacast( "long", arguments.amountToAdd ),
+ this.CHRONO_UNIT[ arguments.unit ]
+ );
+ return this;
+ }
+
+ Duration function plusDays( required daysToAdd ){
+ return this.plus( arguments.daysToAdd, "days" );
+ }
+
+ Duration function plusHours( required hoursToAdd ){
+ return this.plus( arguments.hoursToAdd, "hours" );
+ }
+
+ Duration function plusMillis( required millisToAdd ){
+ return this.plus( arguments.millisToAdd, "millis" );
+ }
+
+ Duration function plusMinutes( required minutesToAdd ){
+ return this.plus( arguments.minutesToAdd, "minutes" );
+ }
+
+ Duration function plusNanos( required nanosToAdd ){
+ return this.plus( arguments.nanosToAdd, "nanos" );
+ }
+
+ Duration function plusSeconds( required secondsToAdd ){
+ return this.plus( arguments.secondsToAdd, "seconds" );
+ }
+
+ /**
+ * --------------------------------------------------------------------------
+ * Creation Methods
+ * --------------------------------------------------------------------------
+ */
+
+ /**
+ * Returns a copy of this duration with the specified nano-of-second.
+ * This returns a duration with the specified nano-of-second, retaining the seconds part of this duration.
+ *
+ * @nanoOfSecond the nano-of-second to represent, from 0 to 999,999,999
+ */
+ function withNanos( required nanoOfSecond ){
+ variables.jDuration = variables.jDuration.withNanos( javacast( "long", arguments.nanoOfSecond ) );
+ return this;
+ }
+
+ /**
+ * Returns a copy of this duration with the specified amount of seconds.
+ * This returns a duration with the specified seconds, retaining the nano-of-second part of this duration.
+ *
+ * @seconds the seconds to represent, may be negative
+ */
+ function withSeconds( required seconds ){
+ variables.jDuration = variables.jDuration.withSeconds( javacast( "long", arguments.seconds ) );
+ return this;
+ }
+
+ /**
+ * Obtains a Duration from a text string such as PnDTnHnMn.nS.
+ *
+ * This will parse a textual representation of a duration, including the string produced by toString(). The formats accepted are based on the ISO-8601 duration format PnDTnHnMn.nS with days considered to be exactly 24 hours.
+ *
+ * Examples:
+ * "PT20.345S" -- parses as "20.345 seconds"
+ * "PT15M" -- parses as "15 minutes" (where a minute is 60 seconds)
+ * "PT10H" -- parses as "10 hours" (where an hour is 3600 seconds)
+ * "P2D" -- parses as "2 days" (where a day is 24 hours or 86400 seconds)
+ * "P2DT3H4M" -- parses as "2 days, 3 hours and 4 minutes"
+ * "PT-6H3M" -- parses as "-6 hours and +3 minutes"
+ * "-PT6H3M" -- parses as "-6 hours and -3 minutes"
+ * "-PT-6H+3M" -- parses as "+6 hours and -3 minutes"
+ *
+ * @see https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/Duration.html#parse(java.lang.CharSequence)
+ *
+ * @text The string to parse and build up to a duration
+ */
+ Duration function parse( required text ){
+ variables.jDuration = variables.jDuration.parse( arguments.text );
+ return this;
+ }
+
+ /**
+ * Obtains an instance of Duration from another duration
+ *
+ * @amount
+ */
+ function from( required Duration amount ){
+ variables.jDuration = variables.jDuration.from( arguments.amount.getNative() );
+ return this;
+ }
+
+ /**
+ * Obtains a Duration representing an amount in the specified unit (seconds)
+ *
+ * @amount the amount of the duration, measured in terms of the unit, positive or negative
+ * @unit The time unit: CENTURIES,DAYS,DECADES,ERAS,FOREVER,HALF_DAYS,HOURS,MICROS,MILLENNIA,MILLIS,MINUTES,MONTHS,NANOS,SECONDS,WEEKS,YEARS
+ */
+ Duration function of( required amount, unit = "seconds" ){
+ variables.jDuration = variables.jDuration.of(
+ javacast( "long", arguments.amount ),
+ this.CHRONO_UNIT[ arguments.unit ]
+ );
+ return this;
+ }
+
+ /**
+ * Obtains a Duration representing a number of standard 24 hour days.
+ *
+ * @days The number of days, positive or negative
+ */
+ Duration function ofDays( required days ){
+ return this.of( arguments.days, "days" );
+ }
+
+ /**
+ * Obtains a Duration representing a number of standard hours.
+ *
+ * @hours The number of hours, positive or negative
+ */
+ Duration function ofHours( required hours ){
+ return this.of( arguments.hours, "hours" );
+ }
+
+ /**
+ * Obtains a Duration representing a number of standard minutes
+ *
+ * @minutes The number of minutes, positive or negative
+ */
+ Duration function ofMinutes( required minutes ){
+ return this.of( arguments.minutes, "minutes" );
+ }
+
+ /**
+ * Obtains a Duration representing a number of seconds and/or an adjustment in nanoseconds.
+ *
+ * This method allows an arbitrary number of nanoseconds to be passed in.
+ * The factory will alter the values of the second and nanosecond in order to ensure that the stored nanosecond is in the range
+ * 0 to 999,999,999. For example, the following will result in exactly the same duration:
+ *
+ * @seconds The number of seconds, positive or negative
+ * @nanoAdjustment the nanosecond adjustment to the number of seconds, positive or negative
+ */
+ Duration function ofSeconds( required seconds, nanoAdjustment ){
+ if ( isNull( arguments.nanoAdjustment ) ) {
+ return this.of( arguments.seconds, "seconds" );
+ }
+ // Width Adjustment
+ variables.jDuration = variables.jDuration.ofSeconds(
+ javacast( "long", arguments.seconds ),
+ javacast( "long", arguments.nanoAdjustment )
+ );
+ return this;
+ }
+
+ /**
+ * Obtains a Duration representing a number of standard milliseconds
+ *
+ * @millis The number of millis, positive or negative
+ */
+ Duration function ofMillis( required millis ){
+ return this.of( arguments.millis, "millis" );
+ }
+
+ /**
+ * Obtains a Duration representing a number of standard nanoseconds
+ *
+ * @nanos The number of nanos, positive or negative
+ */
+ Duration function ofNanos( required nanos ){
+ return this.of( arguments.nanos, "nanos" );
+ }
+
+}
diff --git a/system/async/time/Period.cfc b/system/async/time/Period.cfc
new file mode 100644
index 000000000..8dd7510e4
--- /dev/null
+++ b/system/async/time/Period.cfc
@@ -0,0 +1,391 @@
+/**
+ * A date-based amount of time in the ISO-8601 calendar system, such as '2 years, 3 months and 4 days'.
+ *
+ * This class models a quantity or amount of time in terms of years, months and days. See Duration for the time-based equivalent to this class.
+ *
+ * Durations and periods differ in their treatment of daylight savings time when added to ZonedDateTime.
+ * A Duration will add an exact number of seconds, thus a duration of one day is always exactly 24 hours. By contrast, a Period will add a
+ * conceptual day, trying to maintain the local time.
+ *
+ * For example, consider adding a period of one day and a duration of one day to 18:00 on the evening before a daylight savings gap.
+ * The Period will add the conceptual day and result in a ZonedDateTime at 18:00 the following day. By contrast, the Duration will add exactly
+ * 24 hours, resulting in a ZonedDateTime at 19:00 the following day (assuming a one hour DST gap).
+ *
+ * @see https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/Period.html
+ */
+component accessors="true" {
+
+ // The static java class we represent
+ variables.jPeriod = createObject( "java", "java.time.Period" );
+ // A standard set of date periods units.
+ this.CHRONO_UNIT = new ChronoUnit();
+
+ /**
+ * Initialize to zero date base period
+ *
+ * @years The years
+ * @months The months
+ * @days The days
+ */
+ Period function init( years = 0, months = 0, days = 0 ){
+ return this.of( argumentCollection = arguments );
+ }
+
+ /**
+ * --------------------------------------------------------------------------
+ * Utility Methods
+ * --------------------------------------------------------------------------
+ */
+
+ /**
+ * Get the native java class we proxy to.
+ *
+ * @return java.time.Period
+ */
+ any function getNative(){
+ return variables.jPeriod;
+ }
+
+ /**
+ * Checks if the period is negative, excluding zero
+ */
+ boolean function isNegative(){
+ return variables.jPeriod.isNegative();
+ }
+
+ /**
+ * Checks if the period is zero length
+ */
+ boolean function isZero(){
+ return variables.jPeriod.isZero();
+ }
+
+ /**
+ * Checks if this period is equal to the specified period.
+ *
+ * @otherPeriod The period to compare against
+ */
+ boolean function isEquals( required Period otherPeriod ){
+ return variables.jPeriod.equals( arguments.otherPeriod.getNative() );
+ }
+
+ /**
+ * Returns a copy of this duration with the length negated.
+ */
+ Period function negated(){
+ variables.jPeriod = variables.jPeriod.negated();
+ return this;
+ }
+
+ /**
+ * Returns a copy of this period with the years and months normalized.
+ */
+ Period function normalized(){
+ variables.jPeriod = variables.jPeriod.normalized();
+ return this;
+ }
+
+ /**
+ * --------------------------------------------------------------------------
+ * Retrieval Methods
+ * --------------------------------------------------------------------------
+ */
+
+ /**
+ * Gets the value of the requested unit in seconds by default (days) or years, months, days
+ *
+ * @unit years, months, days
+ *
+ * @throws UnsupportedTemporalTypeException All other units throw an exception.
+ */
+ numeric function get( unit = "days" ){
+ return variables.jPeriod.get( this.CHRONO_UNIT[ arguments.unit ] );
+ }
+
+ /**
+ * Gets the chronology of this period, which is the ISO calendar system.
+ *
+ * @return java.time.chrono.IsoChronology
+ */
+ function getChronology(){
+ return variables.jPeriod.getChronology();
+ }
+
+ /**
+ * Gets the number of Days in this period.
+ */
+ numeric function getDays(){
+ return this.get( "days" );
+ }
+
+ /**
+ * Gets the number of Months in this period.
+ */
+ numeric function getMonths(){
+ return this.get( "months" );
+ }
+
+ /**
+ * Gets the number of Years in this period.
+ */
+ numeric function getYears(){
+ return this.get( "years" );
+ }
+
+ /**
+ * Gets the set (array) of units supported by this period.
+ */
+ array function getUnits(){
+ return arrayMap( variables.jPeriod.getUnits(), function( thisItem ){
+ return arguments.thisItem.name();
+ } );
+ }
+
+ /**
+ * Gets the total number of months in this period.
+ */
+ numeric function toTotalMonths(){
+ return variables.jPeriod.toTotalMonths();
+ }
+
+ /**
+ * Outputs this period as a String, such as P6Y3M1D.
+ */
+ string function toString(){
+ return variables.jPeriod.toString();
+ }
+
+ /**
+ * --------------------------------------------------------------------------
+ * Operations Methods
+ * --------------------------------------------------------------------------
+ */
+
+ /**
+ * Adds this period to the specified temporal object and return back to you a date/time object
+ *
+ * @target The date/time object or string to incorporate the period into
+ * @asNative If true, we will give you the java.time.LocalDate object, else a ColdFusion date/time string
+ *
+ * @return The date/time object with the period added to it or a java LocalDate
+ */
+ function addTo( required target, boolean asNative = false ){
+ var results = variables.jPeriod.addTo( this.CHRONO_UNIT.toLocalDate( arguments.target ) );
+ return ( arguments.asNative ? results : results.toString() );
+ }
+
+ /**
+ * Subtracts this period to the specified temporal object and return back to you a date/time object or a Java LocalDate object
+ *
+ * @target The date/time object or string to incorporate the period into
+ * @asNative If true, we will give you the java.time.LocalDate object, else a ColdFusion date/time string
+ *
+ * @return Return the result either as a date/time string or a java.time.LocalDate object
+ */
+ function subtractFrom( required target, boolean asNative = false ){
+ var results = variables.jPeriod.subtractFrom( this.CHRONO_UNIT.toLocalDate( arguments.target ) );
+ return ( arguments.asNative ? results : results.toString() );
+ }
+
+ /**
+ * Obtains a Duration representing the duration between two temporal date objects.
+ *
+ * This calculates the duration between two temporal objects. If the objects are of different types,
+ * then the duration is calculated based on the type of the first object. For example, if the first argument is a
+ * LocalTime then the second argument is converted to a LocalTime.
+ *
+ * @start The start date/time object
+ * @end The end date/time object
+ */
+ Period function between( required start, required end ){
+ // Do it!
+ variables.jPeriod = variables.jPeriod.between(
+ this.CHRONO_UNIT.toJavaDate( arguments.start ),
+ this.CHRONO_UNIT.toJavaDate( arguments.end )
+ );
+ return this;
+ }
+
+ /**
+ * Returns a copy of this period with the specified period subtracted.
+ *
+ * @amountToSubtract The period to subtract
+ */
+ Period function minus( required Period amountToSubtract ){
+ variables.jPeriod = variables.jPeriod.minus( arguments.amountToSubtract.getNative() );
+ return this;
+ }
+
+ Period function minusDays( required daysToSubtract ){
+ variables.jPeriod = variables.jPeriod.minusDays( javacast( "long", arguments.daysToSubtract ) );
+ return this;
+ }
+
+ Period function minusMonths( required monthsToSubtract ){
+ variables.jPeriod = variables.jPeriod.minusMonths( javacast( "long", arguments.monthsToSubtract ) );
+ return this;
+ }
+
+ Period function minusYears( required yearsToSubtract ){
+ variables.jPeriod = variables.jPeriod.minusYears( javacast( "long", arguments.yearsToSubtract ) );
+ return this;
+ }
+
+ Period function multipliedBy( required scalar ){
+ variables.jPeriod = variables.jPeriod.multipliedBy( javacast( "int", arguments.scalar ) );
+ return this;
+ }
+
+ /**
+ * Returns a copy of this period with the specified period added.
+ *
+ * @amountToAdd The period to Add
+ */
+ Period function plus( required Period amountToAdd ){
+ variables.jPeriod = variables.jPeriod.plus( arguments.amountToAdd.getNative() );
+ return this;
+ }
+
+ Period function plusDays( required daysToAdd ){
+ variables.jPeriod = variables.jPeriod.plusDays( javacast( "long", arguments.daysToAdd ) );
+ return this;
+ }
+
+ Period function plusMonths( required monthsToAdd ){
+ variables.jPeriod = variables.jPeriod.plusMonths( javacast( "long", arguments.monthsToAdd ) );
+ return this;
+ }
+
+ Period function plusYears( required yearsToAdd ){
+ variables.jPeriod = variables.jPeriod.plusYears( javacast( "long", arguments.yearsToAdd ) );
+ return this;
+ }
+
+ /**
+ * --------------------------------------------------------------------------
+ * Creation Methods
+ * --------------------------------------------------------------------------
+ */
+
+ /**
+ * Returns a copy of this period with the specified amount of days.
+ *
+ * @days The days
+ */
+ function withDays( required days ){
+ variables.jPeriod = variables.jPeriod.withDays( javacast( "long", arguments.days ) );
+ return this;
+ }
+
+ /**
+ * Returns a copy of this period with the specified amount of months.
+ *
+ * @months The months
+ */
+ function withMonths( required months ){
+ variables.jPeriod = variables.jPeriod.withMonths( javacast( "long", arguments.months ) );
+ return this;
+ }
+
+ /**
+ * Returns a copy of this period with the specified amount of years.
+ *
+ * @years The years
+ */
+ function withYears( required years ){
+ variables.jPeriod = variables.jPeriod.withYears( javacast( "long", arguments.years ) );
+ return this;
+ }
+
+ /**
+ * Obtains a Period from a text string such as PnYnMnD.
+ *
+ * This will parse the string produced by toString() which is based on the ISO-8601 period formats PnYnMnD and PnW.
+ *
+ * Examples:
+ *
+ * "P2Y" -- Period.ofYears(2)
+ * "P3M" -- Period.ofMonths(3)
+ * "P4W" -- Period.ofWeeks(4)
+ * "P5D" -- Period.ofDays(5)
+ * "P1Y2M3D" -- Period.of(1, 2, 3)
+ * "P1Y2M3W4D" -- Period.of(1, 2, 25)
+ * "P-1Y2M" -- Period.of(-1, 2, 0)
+ * "-P1Y2M" -- Period.of(-1, -2, 0)
+ *
+ * @see https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/Period.html#parse(java.lang.CharSequence)
+ *
+ * @text The string to parse and build up to a period
+ */
+ Period function parse( required text ){
+ variables.jPeriod = variables.jPeriod.parse( arguments.text );
+ return this;
+ }
+
+ /**
+ * Obtains an instance of Period from another period
+ *
+ * @amount The period
+ */
+ function from( required Period amount ){
+ variables.jPeriod = variables.jPeriod.from( arguments.amount.getNative() );
+ return this;
+ }
+
+ /**
+ * Obtains a Period representing an amount in the specified arguments
+ *
+ * @years The years
+ * @months The months
+ * @days The days
+ */
+ Period function of( years = 0, months = 0, days = 0 ){
+ variables.jPeriod = variables.jPeriod.of(
+ javacast( "int", arguments.years ),
+ javacast( "int", arguments.months ),
+ javacast( "int", arguments.days )
+ );
+ return this;
+ }
+
+ /**
+ * Obtains a Period representing a number of days.
+ *
+ * @days The number of days, positive or negative
+ */
+ Period function ofDays( required days ){
+ variables.jPeriod = variables.jPeriod.ofDays( javacast( "int", arguments.days ) );
+ return this;
+ }
+
+ /**
+ * Obtains a Period representing a number of months.
+ *
+ * @months The number of months, positive or negative
+ */
+ Period function ofMonths( required months ){
+ variables.jPeriod = variables.jPeriod.ofMonths( javacast( "int", arguments.months ) );
+ return this;
+ }
+
+ /**
+ * Obtains a Period representing a number of weeks.
+ *
+ * @weeks The number of weeks, positive or negative
+ */
+ Period function ofWeeks( required weeks ){
+ variables.jPeriod = variables.jPeriod.ofWeeks( javacast( "int", arguments.weeks ) );
+ return this;
+ }
+
+ /**
+ * Obtains a Period representing a number of years.
+ *
+ * @years The number of years, positive or negative
+ */
+ Period function ofYears( required years ){
+ variables.jPeriod = variables.jPeriod.ofYears( javacast( "int", arguments.years ) );
+ return this;
+ }
+
+}
diff --git a/system/async/util/TimeUnit.cfc b/system/async/time/TimeUnit.cfc
similarity index 58%
rename from system/async/util/TimeUnit.cfc
rename to system/async/time/TimeUnit.cfc
index cf7ef2eba..1a9ff1ff7 100644
--- a/system/async/util/TimeUnit.cfc
+++ b/system/async/time/TimeUnit.cfc
@@ -1,5 +1,7 @@
/**
- * Static class to map ColdFusion string timeouts to Java Timeouts
+ * Static class to map ColdFusion strings units to Java units
+ * A TimeUnit does not maintain time information,
+ * but only helps organize and use time representations that may be maintained separately across various contexts
*/
component singleton {
@@ -16,25 +18,25 @@ component singleton {
function get( required timeUnit = "milliseconds" ){
switch ( arguments.timeUnit ) {
case "days": {
- return jTimeUnit.DAYS;
+ return variables.jTimeUnit.DAYS;
}
case "hours": {
- return jTimeUnit.HOURS;
+ return variables.jTimeUnit.HOURS;
}
case "microseconds": {
- return jTimeUnit.MICROSECONDS;
+ return variables.jTimeUnit.MICROSECONDS;
}
case "milliseconds": {
- return jTimeUnit.MILLISECONDS;
+ return variables.jTimeUnit.MILLISECONDS;
}
case "minutes": {
- return jTimeUnit.MINUTES;
+ return variables.jTimeUnit.MINUTES;
}
case "nanoseconds": {
- return jTimeUnit.NANOSECONDS;
+ return variables.jTimeUnit.NANOSECONDS;
}
case "seconds": {
- return jTimeUnit.SECONDS;
+ return variables.jTimeUnit.SECONDS;
}
}
}
diff --git a/system/cache/CacheFactory.cfc b/system/cache/CacheFactory.cfc
index 622fbd598..c6ecbe0f4 100644
--- a/system/cache/CacheFactory.cfc
+++ b/system/cache/CacheFactory.cfc
@@ -7,7 +7,7 @@
* 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{
+component accessors=true serializable=false {
/**
* The unique factory id
@@ -60,7 +60,7 @@
/**
* The logBox task scheduler executor
- * @see coldbox.system.async.tasks.ScheduledExecutor
+ * @see coldbox.system.async.executors.ScheduledExecutor
*/
property name="taskScheduler";
@@ -74,28 +74,23 @@
* @factoryID A unique ID or name for this factory. If not passed I will make one up for you.
* @wirebox A configured wirebox instance to get logbox, asyncManager, and EventManager from. If not passed, I will create new ones.
*/
- function init(
- config,
- coldbox,
- factoryId="",
- wirebox
- ){
+ function init( config, coldbox, factoryId = "", wirebox ){
var defaultConfigPath = "coldbox.system.cache.config.DefaultConfiguration";
// CacheBox Factory UniqueID
- variables.factoryId = createObject( "java", "java.lang.System" ).identityHashCode( this );
+ variables.factoryId = createObject( "java", "java.lang.System" ).identityHashCode( this );
// Version
- variables.version = "@build.version@+@build.number@";
+ variables.version = "@build.version@+@build.number@";
// Configuration object
- variables.config = "";
+ variables.config = "";
// ColdBox Application Link
- variables.coldbox = "";
+ variables.coldbox = "";
// ColdBox Application Link
- variables.wirebox = "";
+ variables.wirebox = "";
// Event Manager Link
variables.eventManager = "";
// Configured Event States
- variables.eventStates = [
+ variables.eventStates = [
"afterCacheElementInsert",
"afterCacheElementRemoved",
"afterCacheElementExpired",
@@ -112,13 +107,13 @@
"afterCacheShutdown"
];
// LogBox Links
- variables.logBox = "";
- variables.log = "";
+ variables.logBox = "";
+ variables.log = "";
// Caches
- variables.caches = {};
+ variables.caches = {};
// Did we send a factoryID in?
- if( len( arguments.factoryID ) ){
+ if ( len( arguments.factoryID ) ) {
variables.factoryID = arguments.factoryID;
}
@@ -126,51 +121,53 @@
variables.lockName = "CacheFactory.#variables.factoryID#";
// Passed in configuration?
- if( isNull( arguments.config ) ){
+ if ( isNull( arguments.config ) ) {
// Create default configuration
- arguments.config = new coldbox.system.cache.config.CacheBoxConfig( CFCConfigPath=defaultConfigPath );
- } else if( isSimpleValue( arguments.config ) ){
- arguments.config = new coldbox.system.cache.config.CacheBoxConfig( CFCConfigPath=arguments.config );
+ arguments.config = new coldbox.system.cache.config.CacheBoxConfig( CFCConfigPath = defaultConfigPath );
+ } else if ( isSimpleValue( arguments.config ) ) {
+ arguments.config = new coldbox.system.cache.config.CacheBoxConfig( CFCConfigPath = arguments.config );
}
// Check if linking ColdBox
- if( !isNull( arguments.coldbox ) ){
+ if ( !isNull( arguments.coldbox ) ) {
// Link ColdBox
- variables.coldbox = arguments.coldbox;
+ variables.coldbox = arguments.coldbox;
// Link to WireBox
- variables.wirebox = variables.coldbox.getWireBox();
+ variables.wirebox = variables.coldbox.getWireBox();
// link LogBox
- variables.logBox = variables.coldbox.getLogBox();
+ variables.logBox = variables.coldbox.getLogBox();
// Link Event Manager
variables.eventManager = variables.coldbox.getInterceptorService();
// Link Interception States
variables.coldbox.getInterceptorService().appendInterceptionPoints( variables.eventStates );
// Link async manager and scheduler
- variables.asyncManager = variables.coldbox.getAsyncManager();
+ variables.asyncManager = variables.coldbox.getAsyncManager();
variables.taskScheduler = variables.asyncManager.getExecutor( "coldbox-tasks" );
} else {
- if( !isNull( arguments.wirebox ) ) {
+ if ( !isNull( arguments.wirebox ) ) {
// Link to WireBox
- variables.wirebox = arguments.wirebox;
+ variables.wirebox = arguments.wirebox;
// If WireBox linked, get LogBox and EventManager, and asyncmanager from it
- variables.asyncManager = variables.wirebox.getAsyncManager();
+ variables.asyncManager = variables.wirebox.getAsyncManager();
variables.taskScheduler = variables.wirebox.getTaskScheduler();
- variables.logBox = variables.wirebox.getLogBox();
+ variables.logBox = variables.wirebox.getLogBox();
// link LogBox
- variables.eventManager = variables.wirebox.getEventManager();
+ variables.eventManager = variables.wirebox.getEventManager();
// register the points to listen to
variables.eventManager.appendInterceptionPoints( variables.eventStates );
} else {
-
- // Register an async manager and scheduler
- variables.asyncManager = new coldbox.system.async.AsyncManager();
- variables.taskScheduler = variables.asyncManager.newScheduledExecutor( name : "cachebox-tasks", threads : 20 );
-
- // Running standalone, so create our own logging first
- configureLogBox( arguments.config.getLogBoxConfig() );
- // Running standalone, so create our own event manager
- configureEventManager();
- }
+ // Register an async manager and scheduler
+ variables.asyncManager = new coldbox.system.async.AsyncManager();
+ variables.taskScheduler = variables.asyncManager.newScheduledExecutor(
+ name : "cachebox-tasks",
+ threads: 20
+ );
+
+ // 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
@@ -188,15 +185,16 @@
* @throws CacheBox.ListenerCreationException
*/
CacheFactory function registerListeners(){
- variables.config.getListeners()
+ variables.config
+ .getListeners()
.each( function( item ){
// try to create it
- try{
+ try {
// create it
var thisListener = createObject( "component", item.class );
// configure it
thisListener.configure( this, item.properties );
- } catch( Any e ){
+ } catch ( Any e ) {
throw(
message = "Error creating listener: #item.toString()#",
detail = "#e.message# #e.detail# #e.stackTrace#",
@@ -217,12 +215,7 @@
* @config.doc_generic coldbox.system.cache.config.CacheBoxConfig
*/
function configure( required config ){
- lock
- name="#variables.lockName#"
- type="exclusive"
- timeout="30"
- throwontimeout="true"
- {
+ lock name="#variables.lockName#" type="exclusive" timeout="30" throwontimeout="true" {
// Store config object
variables.config = arguments.config;
// Validate configuration
@@ -231,7 +224,7 @@
variables.caches = {};
// Register Listeners if not using ColdBox
- if( not isObject( variables.coldbox ) ){
+ if ( not isObject( variables.coldbox ) ) {
registerListeners();
}
@@ -244,22 +237,23 @@
);
// Register named caches
- variables.config.getCaches()
+ variables.config
+ .getCaches()
.each( function( key, def ){
createCache(
name = key,
provider = def.provider,
properties = def.properties
);
- });
+ } );
// Scope registrations
- if( variables.config.getScopeRegistration().enabled ){
+ if ( variables.config.getScopeRegistration().enabled ) {
doScopeRegistration();
}
// Announce To Listeners
- variables.eventManager.announce( "afterCacheFactoryConfiguration", { cacheFactory = this } );
+ variables.eventManager.announce( "afterCacheFactoryConfiguration", { cacheFactory : this } );
}
}
@@ -274,8 +268,8 @@
* @return coldbox.system.cache.providers.ICacheProvider
*/
function getCache( required name ){
- lock name="#variables.lockName#" type="readonly" timeout="20" throwontimeout="true"{
- if( variables.caches.keyExists( arguments.name ) ){
+ lock name="#variables.lockName#" type="readonly" timeout="20" throwontimeout="true" {
+ if ( variables.caches.keyExists( arguments.name ) ) {
return variables.caches[ arguments.name ];
}
throw(
@@ -309,7 +303,7 @@
var defaultCacheConfig = variables.config.getDefaultCache();
// Check length
- if( len( arguments.name ) eq 0 ){
+ 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",
@@ -318,7 +312,7 @@
}
// Check it does not exist already
- if( cacheExists( arguments.name ) ){
+ if ( cacheExists( arguments.name ) ) {
throw(
message = "Cache #arguments.name# already exists",
detail = "Cannot register named cache as it already exists in the registry",
@@ -339,12 +333,12 @@
*/
CacheFactory function shutdown(){
// Log startup
- if( variables.log.canDebug() ){
+ if ( variables.log.canDebug() ) {
variables.log.debug( "Shutdown of cache factory: #getFactoryID()# requested and started." );
}
// Notify Listeners
- variables.eventManager.announce( "beforeCacheFactoryShutdown", { cacheFactory = this } );
+ variables.eventManager.announce( "beforeCacheFactoryShutdown", { cacheFactory : this } );
// safely iterate and shutdown caches
getCacheNames().each( function( item ){
@@ -352,21 +346,21 @@
var cache = getCache( item );
// Log it
- if( variables.log.canDebug() ){
+ if ( variables.log.canDebug() ) {
variables.log.debug( "Shutting down cache: #item# on factoryID: #getFactoryID()#." );
}
- //process listeners
- variables.eventManager.announce( "beforeCacheShutdown", { cache = cache } );
+ // process listeners
+ variables.eventManager.announce( "beforeCacheShutdown", { cache : cache } );
- //Shutdown each cache
+ // Shutdown each cache
cache.shutdown();
- //process listeners
- variables.eventManager.announce( "afterCacheShutdown", { cache = cache } );
+ // process listeners
+ variables.eventManager.announce( "afterCacheShutdown", { cache : cache } );
// log
- if( variables.log.canDebug() ){
+ if ( variables.log.canDebug() ) {
variables.log.debug( "Cache: #item# was shut down on factoryID: #getFactoryID()#." );
}
} );
@@ -378,18 +372,18 @@
removeFromScope();
// Shutdown LogBox and Executors if not in ColdBox Mode or WireBox mode
- if( !isObject( variables.coldbox ) && !isObject( variables.wirebox ) ){
- if( isObject( variables.logBox ) ) {
+ if ( !isObject( variables.coldbox ) && !isObject( variables.wirebox ) ) {
+ if ( isObject( variables.logBox ) ) {
variables.logBox.shutdown();
}
variables.asyncManager.shutdownAllExecutors( force = true );
}
// Notify Listeners
- variables.eventManager.announce( "afterCacheFactoryShutdown", { cacheFactory = this } );
+ variables.eventManager.announce( "afterCacheFactoryShutdown", { cacheFactory : this } );
// Log shutdown complete
- if( variables.log.canDebug() ){
+ if ( variables.log.canDebug() ) {
variables.log.debug( "Shutdown of cache factory: #getFactoryID()# completed." );
}
@@ -402,14 +396,16 @@
* @name The name of the cache to shutdown
*/
CacheFactory function shutdownCache( required name ){
- var iData = {};
- var cache = "";
- var i = 1;
+ 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." );
+ if ( NOT cacheExists( arguments.name ) ) {
+ if ( variables.log.canWarn() ) {
+ variables.log.warn(
+ "Trying to shutdown #arguments.name#, but that cache does not exist, skipping."
+ );
}
return this;
}
@@ -418,25 +414,29 @@
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()#" );
+ if ( variables.log.canDebug() ) {
+ variables.log.debug(
+ "Shutdown of cache: #arguments.name# requested and started on factoryID: #getFactoryID()#"
+ );
}
// Notify Listeners
- variables.eventManager.announce( "beforeCacheShutdown", { cache = cache } );
+ variables.eventManager.announce( "beforeCacheShutdown", { cache : cache } );
- //Shutdown the cache
+ // Shutdown the cache
cache.shutdown();
- //process listeners
- variables.eventManager.announce( "afterCacheShutdown", { cache = cache } );
+ // process listeners
+ variables.eventManager.announce( "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()#." );
+ if ( variables.log.canDebug() ) {
+ variables.log.debug(
+ "Cache: #arguments.name# was shut down and removed on factoryID: #getFactoryID()#."
+ );
}
return this;
@@ -447,9 +447,8 @@
*/
CacheFactory function removeFromScope(){
var scopeInfo = variables.config.getScopeRegistration();
- if( scopeInfo.enabled ){
- new coldbox.system.core.collections.ScopeStorage()
- .delete( scopeInfo.key, scopeInfo.scope );
+ if ( scopeInfo.enabled ) {
+ new coldbox.system.core.collections.ScopeStorage().delete( scopeInfo.key, scopeInfo.scope );
}
return this;
}
@@ -460,19 +459,21 @@
* @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()#" );
+ 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()#"
+ );
}
// Retrieve it
var cache = variables.caches[ arguments.name ];
// Notify listeners here
- variables.eventManager.announce( "beforeCacheRemoval", { cache = cache } );
+ variables.eventManager.announce( "beforeCacheRemoval", { cache : cache } );
// process shutdown
cache.shutdown();
@@ -481,10 +482,10 @@
structDelete( variables.caches, arguments.name );
// Announce it
- variables.eventManager.announce( "afterCacheRemoval", { cache = arguments.name } );
+ variables.eventManager.announce( "afterCacheRemoval", { cache : arguments.name } );
// Log it
- if( variables.log.canDebug() ){
+ if ( variables.log.canDebug() ) {
variables.log.debug( "Cache: #arguments.name# removed from factory: #getFactoryID()#" );
}
@@ -493,8 +494,10 @@
}
}
- if( variables.log.canDebug() ){
- variables.log.debug( "Cache: #arguments.name# not removed because it does not exist in registered caches: #arrayToList( getCacheNames() )#. FactoryID: #getFactoryID()#" );
+ 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;
@@ -504,7 +507,7 @@
* Remove all the registered caches in this factory, this triggers individual cache shutdowns
*/
CacheFactory function removeAll(){
- if( variables.log.canDebug() ){
+ if ( variables.log.canDebug() ) {
variables.log.debug( "Removal of all caches requested on factoryID: #getFactoryID()#" );
}
@@ -513,7 +516,7 @@
} );
- if( variables.log.canDebug() ){
+ if ( variables.log.canDebug() ) {
variables.log.debug( "All caches removed." );
}
@@ -524,7 +527,7 @@
* A nice way to call reap on all registered caches
*/
CacheFactory function reapAll(){
- if( variables.log.canDebug() ){
+ if ( variables.log.canDebug() ) {
variables.log.debug( "Executing reap on factoryID: #getFactoryID()#" );
}
@@ -539,7 +542,7 @@
* 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"{
+ lock name="#variables.lockName#" type="readonly" timeout="20" throwontimeout="true" {
return structKeyExists( variables.caches, arguments.name );
}
}
@@ -554,20 +557,20 @@
*/
CacheFactory function replaceCache( required cache, required decoratedCache ){
// determine cache name
- if( isObject( arguments.cache ) ){
+ if ( isObject( arguments.cache ) ) {
var name = arguments.cache.getName();
} else {
var name = arguments.cache;
}
- lock name="#variables.lockName#" type="exclusive" timeout="20" throwontimeout="true"{
+ lock name="#variables.lockName#" type="exclusive" timeout="20" throwontimeout="true" {
// Announce to listeners
var iData = {
- oldCache = variables.caches[ name ],
- newCache = arguments.decoratedCache
+ oldCache : variables.caches[ name ],
+ newCache : arguments.decoratedCache
};
- variables.eventManager.announce( "beforeCacheReplacement", iData );
+ variables.eventManager.announce( "beforeCacheReplacement", iData );
// remove old Cache
structDelete( variables.caches, name );
@@ -576,8 +579,10 @@
variables.caches[ name ] = arguments.decoratedCache;
// debugging
- if( variables.log.canDebug() ){
- variables.log.debug( "Cache #name# replaced with decorated cache: #getMetadata( arguments.decoratedCache ).name# on factoryID: #getFactoryID()#" );
+ if ( variables.log.canDebug() ) {
+ variables.log.debug(
+ "Cache #name# replaced with decorated cache: #getMetadata( arguments.decoratedCache ).name# on factoryID: #getFactoryID()#"
+ );
}
}
@@ -588,7 +593,7 @@
* Clears all the elements in all the registered caches without de-registrations
*/
CacheFactory function clearAll(){
- if( variables.log.canDebug() ){
+ if ( variables.log.canDebug() ) {
variables.log.debug( "Clearing all registered caches of their content on factoryID: #getFactoryID()#" );
}
@@ -603,7 +608,7 @@
* Expires all the elements in all the registered caches without de-registrations
*/
CacheFactory function expireAll(){
- if( variables.log.canDebug() ){
+ if ( variables.log.canDebug() ) {
variables.log.debug( "Expiring all registered caches of their content on factoryID: #getFactoryID()#" );
}
@@ -618,7 +623,7 @@
* Get the array of caches registered with this factory
*/
array function getCacheNames(){
- lock name="#variables.lockName#" type="readonly" timeout="20" throwontimeout="true"{
+ lock name="#variables.lockName#" type="readonly" timeout="20" throwontimeout="true" {
return structKeyArray( variables.caches );
}
}
@@ -655,7 +660,11 @@
*
* @return coldbox.system.cache.providers.ICacheProvider
*/
- function createCache( required name, required provider, struct properties={} ){
+ function createCache(
+ required name,
+ required provider,
+ struct properties = {}
+ ){
// Create Cache
var oCache = createObject( "component", arguments.provider ).init();
// Register Name
@@ -675,8 +684,7 @@
*/
private CacheFactory function doScopeRegistration(){
var scopeInfo = variables.config.getScopeRegistration();
- new coldbox.system.core.collections.ScopeStorage()
- .put( scopeInfo.key, this, scopeInfo.scope );
+ new coldbox.system.core.collections.ScopeStorage().put( scopeInfo.key, this, scopeInfo.scope );
return this;
}
@@ -689,28 +697,25 @@
* @throws CacheFactory.CacheExistsException
*/
private CacheFactory function registerCache( required cache ){
- var name = arguments.cache.getName();
- var oCache = arguments.cache;
+ var name = arguments.cache.getName();
+ var oCache = arguments.cache;
- if( variables.caches.keyExists( name ) ){
- throw(
- message = "Cache #name# already exists!",
- type = "CacheFactory.CacheExistsException"
- );
+ 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 ) ){
+ 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" ) ){
+ if ( isObject( variables.coldbox ) AND structKeyExists( oCache, "setColdBox" ) ) {
oCache.setColdBox( variables.coldbox );
}
// Link WireBox if using it
- if( isObject( variables.wirebox ) AND structKeyExists( oCache, "setWireBox" ) ){
+ if ( isObject( variables.wirebox ) AND structKeyExists( oCache, "setWireBox" ) ) {
oCache.setWireBox( variables.wirebox );
}
// Link Event Manager
@@ -720,7 +725,7 @@
// Store it
variables.caches[ name ] = oCache;
// Announce new cache registration now
- variables.eventManager.announce( "afterCacheRegistration", { cache = oCache } );
+ variables.eventManager.announce( "afterCacheRegistration", { cache : oCache } );
}
}
}
@@ -735,7 +740,7 @@
*/
private CacheFactory function configureLogBox( required configPath ){
// Create config object
- var oConfig = new coldbox.system.logging.config.LogBoxConfig( CFCConfigPath = arguments.configPath );
+ var oConfig = new coldbox.system.logging.config.LogBoxConfig( CFCConfigPath = arguments.configPath );
// Create LogBox standalone and store it
variables.logBox = new coldbox.system.logging.LogBox( oConfig );
return this;
@@ -759,4 +764,4 @@
return new coldbox.system.core.util.Util();
}
- }
\ No newline at end of file
+}
diff --git a/system/core/util/Util.cfc b/system/core/util/Util.cfc
index d8696e09a..46e836ac0 100644
--- a/system/core/util/Util.cfc
+++ b/system/core/util/Util.cfc
@@ -1,14 +1,96 @@
-
-
-
+/**
+ * The main ColdBox utility library, it is built with tags to allow for dumb ACF10 compatibility
+ */
+component {
+
+ /****************************************************************
+ * SERVER/USER/CFML ENGINE HELPERS *
+ ****************************************************************/
+
+ /**
+ * Add a CFML Mapping to the running engine
+ *
+ * @name The name of the mapping
+ * @path The path of the mapping
+ * @mappings A struct of mappings to incorporate instead of one-offs
+ */
+ Util function addMapping( string name, string path, struct mappings ){
+ var mappingHelper = "";
+
+ // Detect server
+ if ( listFindNoCase( "Lucee", server.coldfusion.productname ) ) {
+ mappingHelper = new LuceeMappingHelper();
+ } else {
+ mappingHelper = new CFMappingHelper();
+ }
+
+ 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 );
+ }
+
+ return this;
+ }
+
+ /**
+ * Check if you are in cfthread or not for any CFML Engine
+ *
+ * @path The file target
+ */
+ boolean function inThread(){
+ 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": {
+ return isInThread();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get the hostname of the executing machine.
+ */
+ function discoverInetHost(){
+ try {
+ return createObject( "java", "java.net.InetAddress" ).getLocalHost().getHostName();
+ } catch ( any e ) {
+ return cgi.SERVER_NAME;
+ }
+ }
+
+ /**
+ * Get the server IP Address
+ */
+ function getServerIp(){
+ return ( isNull( cgi.local_addr ) ? "0.0.0.0" : cgi.local_addr );
+ }
+
/**
* Builds the unique Session Key of a user request and returns it to you.
*/
@@ -37,158 +119,70 @@ The main ColdBox utility library, it is built with tags to allow for dumb ACF10
);
}
}
-
-
-
-
-
- if ( structKeyExists( variables, "mixerUtil" ) ) {
- return variables.mixerUtil;
- }
- variables.mixerUtil = new coldbox.system.core.dynamic.MixerUtil();
- return variables.mixerUtil;
-
-
-
-
-
-
-
- return arguments.in.reduce( function( result, item, index ){
- var target = {};
- if ( !isNull( arguments.result ) ) {
- target = arguments.result;
- }
- target[ arguments.index ] = arguments.item;
- return target;
- } );
-
-
-
-
-
-
-
+
+ /****************************************************************
+ * CONVERSTION METHODS *
+ ****************************************************************/
+
+ /**
+ * Convert an array to struct argument notation
+ *
+ * @target The array to convert
+ */
+ struct function arrayToStruct( required array target ){
+ return arguments.target.reduce( function( result, item, index ){
+ arguments.result[ arguments.index ] = arguments.item;
+ return arguments.result;
+ }, {} );
+ }
+
+ /****************************************************************
+ * FILE HELPERS *
+ ****************************************************************/
+
+ /**
+ * Get the last modified date of a file
+ *
+ * @filename The file target
+ */
+ function fileLastModified( required filename ){
return getFileInfo( getAbsolutePath( arguments.filename ) ).lastModified;
-
-
-
-
-
-
-
-
-
-
-
-
-
+ }
+
+ /**
+ * Rip the extension of a filename.
+ *
+ * @filename The file target
+ */
+ function ripExtension( required filename ){
+ return reReplace( arguments.filename, "\.[^.]*$", "" );
+ }
+
+ /**
+ * Turn any system path, either relative or absolute, into a fully qualified one
+ *
+ * @path The file target
+ */
+ function getAbsolutePath( required path ){
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.
+ /****************************************************************
+ * STRING HELPERS *
+ ****************************************************************/
- return false;
-
-
-
-
-
-
-
-
+ /**
+ * PlaceHolder Replacer for strings containing ${}
patterns
+ *
+ * @str The string target
+ * @settings The structure of settings to use in the replacements
+ *
+ * @return The string with the replacements
+ */
+ function placeHolderReplacer( required str, required settings ){
var returnString = arguments.str;
var regex = "\$\{([0-9a-z\-\.\_]+)\}";
var lookup = 0;
@@ -202,11 +196,7 @@ The main ColdBox utility library, it is built with tags to allow for dumb ACF10
// Found?
if ( lookup.pos[ 1 ] ) {
// Get Variable Name From Pattern
- var varName = mid(
- returnString,
- lookup.pos[ 2 ],
- lookup.len[ 2 ]
- );
+ var varName = mid( returnString, lookup.pos[ 2 ], lookup.len[ 2 ] );
var varValue = "VAR_NOT_FOUND";
// Lookup Value
@@ -218,41 +208,30 @@ The main ColdBox utility library, it is built with tags to allow for dumb ACF10
varValue = structFindKey( arguments.settings, varName )[ 1 ].value;
}
// Remove PlaceHolder Entirely
- returnString = removeChars(
- returnString,
- lookup.pos[ 1 ],
- lookup.len[ 1 ]
- );
+ returnString = removeChars( returnString, lookup.pos[ 1 ], lookup.len[ 1 ] );
// Insert Var Value
- returnString = insert(
- varValue,
- returnString,
- lookup.pos[ 1 ] - 1
- );
+ returnString = insert( varValue, returnString, lookup.pos[ 1 ] - 1 );
} else {
break;
}
}
return returnString;
-
-
-
-
-
-
-
-
+ }
+
+ /****************************************************************
+ * ENVIRONMENT METHODS *
+ ****************************************************************/
+
+ /**
+ * Retrieve a Java System property or env value by name. It looks at properties first then environment variables
+ *
+ * @key The name of the setting to look up.
+ * @defaultValue The default value to use if the key does not exist in the system properties or the env
+ *
+ * @throws SystemSettingNotFound When the java system property or env is not found
+ */
+ function getSystemSetting( required key, defaultValue ){
var value = getJavaSystem().getProperty( arguments.key );
if ( !isNull( local.value ) ) {
return value;
@@ -268,27 +247,20 @@ The main ColdBox utility library, it is built with tags to allow for dumb ACF10
}
throw(
- type = "SystemSettingNotFound",
- message = "Could not find a Java System property or Env setting with key [#arguments.key#]."
+ type : "SystemSettingNotFound",
+ message: "Could not find a Java System property or Env setting with key [#arguments.key#]."
);
-
-
-
-
-
-
-
-
+ }
+
+ /**
+ * Retrieve a Java System property value by key
+ *
+ * @key The name of the setting to look up.
+ * @defaultValue The default value to use if the key does not exist in the system properties or the env
+ *
+ * @throws SystemSettingNotFound When the java system property is not found
+ */
+ function getSystemProperty( required key, defaultValue ){
var value = getJavaSystem().getProperty( arguments.key );
if ( !isNull( local.value ) ) {
return value;
@@ -302,24 +274,17 @@ The main ColdBox utility library, it is built with tags to allow for dumb ACF10
type = "SystemSettingNotFound",
message = "Could not find a Java System property with key [#arguments.key#]."
);
-
-
-
-
-
-
-
-
+ }
+
+ /**
+ * Retrieve a Java System environment value by name
+ *
+ * @key The name of the setting to look up.
+ * @defaultValue The default value to use if the key does not exist in the system properties or the env
+ *
+ * @throws SystemSettingNotFound When the java system property is not found
+ */
+ function getEnv( required key, defaultValue ){
var value = getJavaSystem().getEnv( arguments.key );
if ( !isNull( local.value ) ) {
return value;
@@ -333,38 +298,42 @@ The main ColdBox utility library, it is built with tags to allow for dumb ACF10
type = "SystemSettingNotFound",
message = "Could not find a environment variable with key [#arguments.key#]."
);
-
-
-
-
-
-
+ }
+
+ /**
+ * Retrieve an instance of Java System
+ */
+ function getJavaSystem(){
if ( !structKeyExists( variables, "javaSystem" ) ) {
variables.javaSystem = createObject( "java", "java.lang.System" );
}
return variables.javaSystem;
-
-
-
-
-
-
-
-
-
-
+ }
+
+ /**
+ * Get the mixer utility
+ *
+ * @return coldbox.system.core.dynamic.MixerUtil
+ */
+ function getMixerUtil(){
+ if ( structKeyExists( variables, "mixerUtil" ) ) {
+ return variables.mixerUtil;
+ }
+ variables.mixerUtil = new coldbox.system.core.dynamic.MixerUtil();
+ return variables.mixerUtil;
+ }
+
+ /****************************************************************
+ * COLDBOX TAXONOMY Methods *
+ ****************************************************************/
+
+ /**
+ * Checks if an object is of the passed in family type
+ *
+ * @family The family to covert it to: handler, interceptor
+ * @target The target object
+ */
+ boolean function isFamilyType( required family, required target ){
var familyPath = "";
switch ( arguments.family ) {
@@ -382,20 +351,17 @@ The main ColdBox utility library, it is built with tags to allow for dumb ACF10
}
return isInstanceOf( arguments.target, familyPath );
-
-
-
-
-
-
-
-
+ }
+
+ /**
+ * Decorate an object as a ColdBox Family object
+ *
+ * @family The family to convert it to
+ * @target The target object
+ *
+ * @return The same target object
+ */
+ function convertToColdBox( required family, required target ){
var familyPath = "";
switch ( arguments.family ) {
@@ -433,100 +399,17 @@ The main ColdBox utility library, it is built with tags to allow for dumb ACF10
// Mix in fake super class
arguments.target.$super = baseObject;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ return arguments.target;
+ }
+
+ /**
+ * Should we stop recursion or not due to class name found: Boolean
+ *
+ * @className The class name to check
+ * @stopRecursions An array of classes to stop processing for during inheritance trails
+ */
+ private boolean function stopClassRecursion( required classname, required stopRecursions ){
// Try to find a match
for ( var thisClass in arguments.stopRecursions ) {
if ( compareNoCase( thisClass, arguments.classname ) eq 0 ) {
@@ -534,37 +417,88 @@ The main ColdBox utility library, it is built with tags to allow for dumb ACF10
}
}
return false;
-
-
-
-
-
-
-
-
-
- var mappingHelper = "";
+ }
- // Detect server
- if ( listFindNoCase( "Lucee", server.coldfusion.productname ) ) {
- mappingHelper = new LuceeMappingHelper();
- } else {
- mappingHelper = new CFMappingHelper();
+ /**
+ * Returns a single-level metadata struct that includes all items inhereited from extending classes.
+ *
+ * @component The component instance or path to get the metadata from
+ * @stopRecursions An array of classes to stop processing for during inheritance trails
+ * @md A structure containing a copy of the metadata for this level of recursion.
+ *
+ * @return struct of metadata
+ */
+ function getInheritedMetaData(
+ required component,
+ array stopRecursions = [],
+ struct md = {}
+ ){
+ var loc = {};
+
+ // First time through, get metaData of component by path or instance
+ if ( arguments.md.isEmpty() ) {
+ arguments.md = (
+ isObject( arguments.component ) ? getMetadata( arguments.component ) : getComponentMetadata(
+ arguments.component
+ )
+ );
}
- if ( !isNull( arguments.mappings ) ) {
- mappingHelper.addMappings( arguments.mappings );
+ // If it has a parent, stop and calculate it first, unless of course, we've reached a class we shouldn't recurse into.
+ if (
+ structKeyExists( arguments.md, "extends" ) &&
+ arguments.md.type eq "component" &&
+ stopClassRecursion( md.extends.name, arguments.stopRecursions ) EQ FALSE
+ ) {
+ loc.parent = getInheritedMetaData(
+ component = arguments.component,
+ stopRecursions = arguments.stopRecursions,
+ md = arguments.md.extends
+ );
+ // If we're at the end of the line, it's time to start working backwards so start with an empty struct to hold our condensesd metadata.
} else {
- // Add / registration
- if ( left( arguments.name, 1 ) != "/" ) {
- arguments.name = "/#arguments.name#";
- }
+ loc.parent = { "inheritanceTrail" : [] };
+ }
- // Add mapping
- mappingHelper.addMapping( arguments.name, arguments.path );
+ // Override ourselves into parent
+ for ( var thisKey in arguments.md ) {
+ // Functions and properties are an array of structs keyed on name, so I can treat them the same
+ if ( listFindNoCase( "functions,properties", thisKey ) ) {
+ if ( !structKeyExists( loc.parent, thisKey ) ) {
+ loc.parent[ thisKey ] = [];
+ }
+
+ // For each function/property in me...
+ for ( var thisItem in arguments.md[ thisKey ] ) {
+ loc.parentItemCounter = 0;
+ loc.foundInParent = false;
+ // ...Look for an item of the same name in my parent...
+ for ( var thisParentItem in loc.parent[ thisKey ] ) {
+ loc.parentItemCounter++;
+ // ...And override it
+ if ( compareNoCase( thisItem.name, thisParentItem.name ) eq 0 ) {
+ loc.parent[ thisKey ][ loc.parentItemCounter ] = thisItem;
+ loc.foundInParent = true;
+ break;
+ }
+ }
+ // ..Or add it
+ if ( not loc.foundInParent ) {
+ arrayAppend( loc.parent[ thisKey ], thisItem );
+ }
+ }
+ }
+ // Add in anything that's not inheritance or implementation
+ else if ( NOT listFindNoCase( "extends,implements", thisKey ) ) {
+ loc.parent[ thisKey ] = arguments.md[ thisKey ];
+ }
}
- return this;
-
-
-
+ // Store away the inheritance trail
+ arrayPrepend( loc.parent.inheritanceTrail, loc.parent.name );
+
+ // Return our results
+ return loc.parent;
+ }
+
+}
diff --git a/system/ioc/Builder.cfc b/system/ioc/Builder.cfc
index 42092a395..b77ba6737 100644
--- a/system/ioc/Builder.cfc
+++ b/system/ioc/Builder.cfc
@@ -4,7 +4,7 @@
* ---
* The WireBox builder for components, java, etc. I am in charge of building stuff and integration dsl builders.
**/
- component serializable="false" accessors="true"{
+component serializable="false" accessors="true" {
/**
* Injector Reference
@@ -55,25 +55,34 @@
* @return coldbox.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 = {};
+ variables.injector = arguments.injector;
+ variables.logBox = arguments.injector.getLogBox();
+ variables.log = arguments.injector.getLogBox().getlogger( this );
+ variables.utility = arguments.injector.getUtil();
+ variables.customDSL = {};
// Internal DSL Registry
variables.internalDSL = [
- "coldbox", "box", "executor", "cachebox", "logbox", "model", "id", "provider", "wirebox", "java", "byType"
+ "coldbox",
+ "box",
+ "executor",
+ "cachebox",
+ "logbox",
+ "model",
+ "id",
+ "provider",
+ "wirebox",
+ "java",
+ "byType"
];
// Do we need to build the coldbox DSL namespace
- if( variables.injector.isColdBoxLinked() ){
+ if ( variables.injector.isColdBoxLinked() ) {
variables.coldboxDSL = new coldbox.system.ioc.dsl.ColdBoxDSL( arguments.injector );
}
// Is CacheBox Linked?
- if( variables.injector.isCacheBoxLinked() ){
+ if ( variables.injector.isCacheBoxLinked() ) {
variables.cacheBoxDSL = new coldbox.system.ioc.dsl.CacheBoxDSL( arguments.injector );
}
@@ -87,12 +96,12 @@
* Register custom DSL builders with this main wirebox builder
*
*/
- Builder function registerCustomBuilders(){
+ Builder function registerCustomBuilders(){
var customDSL = variables.injector.getBinder().getCustomDSL();
// Register Custom DSL Builders
- for( var key in customDSL ){
- registerDSL( namespace=key, path=customDSL[ key ] );
+ for ( var key in customDSL ) {
+ registerDSL( namespace = key, path = customDSL[ key ] );
}
return this;
}
@@ -103,11 +112,11 @@
* @namespace The namespace you would like to register
* @path The instantiation path to the CFC that implements this scope, it must have an init() method and implement: coldbox.system.ioc.dsl.IDSLBuilder
*/
- Builder function registerDSL( required namespace, required path ){
+ Builder function registerDSL( required namespace, required path ){
// register dsl
variables.customDSL[ arguments.namespace ] = new "#arguments.path#"( variables.injector );
// Debugging
- if( variables.log.canDebug() ){
+ if ( variables.log.canDebug() ) {
variables.log.debug( "Registered custom DSL Builder with namespace: #arguments.namespace#" );
}
return this;
@@ -121,12 +130,12 @@
var targetProvider = this.$wbProviders[ getFunctionCalledName() ];
// Verify if this is a mapping first?
- if( targetInjector.containsInstance( targetProvider ) ){
- return targetInjector.getInstance( name=targetProvider, targetObject=this );
+ if ( targetInjector.containsInstance( targetProvider ) ) {
+ return targetInjector.getInstance( name = targetProvider, targetObject = this );
}
// else treat as full DSL
- return targetInjector.getInstance( dsl=targetProvider, targetObject=this );
+ return targetInjector.getInstance( dsl = targetProvider, targetObject = this );
}
/**
@@ -138,58 +147,75 @@
* @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?
- var constructorArgs = thisMap.getDIConstructorArguments();
- var constructorArgNames = constructorArgs.map( function( arg ) { return arg.name; } );
- if( thisMap.isVirtualInheritance() ){
+ var thisMap = arguments.mapping;
+ var oModel = createObject( "component", thisMap.getPath() );
+
+ // Do we have virtual inheritance?
+ var constructorArgs = thisMap.getDIConstructorArguments();
+ var constructorArgNames = constructorArgs.map( function( arg ){
+ return arg.name;
+ } );
+ if ( thisMap.isVirtualInheritance() ) {
// retrieve the VI mapping.
var viMapping = variables.injector.getBinder().getMapping( thisMap.getVirtualInheritance() );
// Does it match the family already?
- if( NOT isInstanceOf( oModel, viMapping.getPath() ) ){
+ if ( NOT isInstanceOf( oModel, viMapping.getPath() ) ) {
// Virtualize it.
- toVirtualInheritance( viMapping, oModel, thisMap );
-
- // Only add virtual inheritance constructor args if we don't already have one with that name.
- arrayAppend( constructorArgs, viMapping.getDIConstructorArguments().filter( function( arg ) {
- return !arrayContainsNoCase( constructorArgNames, arg.name );
- } ), true );
+ toVirtualInheritance( viMapping, oModel, thisMap );
+ // Only add virtual inheritance constructor args if we don't already have one with that name.
+ arrayAppend(
+ constructorArgs,
+ viMapping
+ .getDIConstructorArguments()
+ .filter( function( arg ){
+ return !arrayContainsNoCase( constructorArgNames, arg.name );
+ } ),
+ true
+ );
}
}
// Constructor initialization?
- if( thisMap.isAutoInit() AND structKeyExists( oModel, thisMap.getConstructor() ) ){
- // Get Arguments
+ if ( thisMap.isAutoInit() AND structKeyExists( oModel, thisMap.getConstructor() ) ) {
+ // Get Arguments
var constructorArgCollection = buildArgumentCollection( thisMap, constructorArgs, oModel );
// Do We have initArguments to override
- if( NOT structIsEmpty( arguments.initArguments ) ){
- structAppend( constructorArgCollection, arguments.initArguments, true );
+ if ( NOT structIsEmpty( arguments.initArguments ) ) {
+ structAppend(
+ constructorArgCollection,
+ arguments.initArguments,
+ true
+ );
}
try {
// Invoke constructor
- invoke( oModel, thisMap.getConstructor(), constructorArgCollection );
- } catch( any e ){
-
- var reducedTagContext = e.tagContext.reduce( function(result, file) {
- if( !result.done ) {
- if( file.template.listLast( '/\' ) == 'Builder.cfc' ) {
+ invoke(
+ oModel,
+ thisMap.getConstructor(),
+ constructorArgCollection
+ );
+ } 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#' );
+ result.rows.append( "#file.template#:#file.line#" );
}
}
return result;
- }, {rows:[],done:false} ).rows.toList( chr(13)&chr(10) );
+ }, { rows : [], done : false } )
+ .rows
+ .toList( chr( 13 ) & chr( 10 ) );
throw(
type = "Builder.BuildCFCDependencyException",
message = "Error building: #thisMap.getName()# -> #e.message#
#e.detail#.",
- detail = "DSL: #thisMap.getDSL()#, Path: #thisMap.getPath()#,
+ detail = "DSL: #thisMap.getDSL()#, Path: #thisMap.getPath()#,
Error Location:
#reducedTagContext#"
);
@@ -197,7 +223,7 @@
}
return oModel;
- }
+ }
/**
* Build an object using a factory method
@@ -207,12 +233,12 @@
* @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;
+ 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 ) ){
+ if ( NOT variables.injector.containsInstance( factoryName ) ) {
throw(
message = "The factory mapping: #factoryName# is not registered with the injector",
type = "Builder.InvalidFactoryMappingException"
@@ -220,20 +246,24 @@
}
// get Factory mapping
- var oFactory = variables.injector.getInstance( factoryName );
+ var oFactory = variables.injector.getInstance( factoryName );
// Get Method Arguments
- var methodArgs = buildArgumentCollection( thisMap, thisMap.getDIMethodArguments(), oFactory );
+ var methodArgs = buildArgumentCollection(
+ thisMap,
+ thisMap.getDIMethodArguments(),
+ oFactory
+ );
// Do we have overrides
- if( NOT structIsEmpty( arguments.initArguments ) ){
+ if ( NOT structIsEmpty( arguments.initArguments ) ) {
structAppend( methodArgs, arguments.initArguments, true );
}
// Get From Factory
var oModel = invoke( oFactory, thisMap.getMethod(), methodArgs );
- //Return factory bean
+ // Return factory bean
return oModel;
- }
+ }
/**
* Build a Java class via mappings
@@ -242,22 +272,22 @@
* @mapping.doc_generic coldbox.system.ioc.config.Mapping
*/
function buildJavaClass( required mapping ){
- var DIArgs = arguments.mapping.getDIConstructorArguments();
- var args = [];
- var thisMap = arguments.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 ) );
+ 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() ){
+ if ( thisMap.isAutoInit() ) {
+ if ( args.len() ) {
return invoke(
createObject( "java", arguments.mapping.getPath() ),
"init",
@@ -279,53 +309,66 @@
* @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 = {};
+ 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 ){
-
+ for ( var thisArg in DIArgs ) {
// Process if we have a value and continue
- if( !isNull( thisArg.value ) ){
+ 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 );
+ 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 );
+ 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 ){
+ if ( thisArg.required ) {
// Log the error
- variables.log.error( "Target: #thisMap.getName()# -> Argument reference not located: #thisArg.name#", thisArg );
+ 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 );
}
-
+ // 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
@@ -333,21 +376,25 @@
* @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();
+ function buildWebservice( required mapping, initArguments = {} ){
+ var argStruct = {};
+ var DIArgs = arguments.mapping.getDIConstructorArguments();
// Process args
- for( var thisArg in DIArgs ){
+ for ( var thisArg in DIArgs ) {
argStruct[ thisArg.name ] = thisArg.value;
}
// Do we have overrides
- if( NOT structIsEmpty( arguments.initArguments ) ){
+ if ( NOT structIsEmpty( arguments.initArguments ) ) {
structAppend( argStruct, arguments.initArguments, true );
}
- return createObject( "webservice", arguments.mapping.getPath(), argStruct );
+ return createObject(
+ "webservice",
+ arguments.mapping.getPath(),
+ argStruct
+ );
}
/**
@@ -357,20 +404,20 @@
* @mapping.doc_generic coldbox.system.ioc.config.Mapping
*/
function buildFeed( required mapping ){
- var results = {};
+ var results = {};
var feedAttributes = {
- action = "read",
- source = arguments.mapping.getPath(),
- query = "results.items",
- properties = "results.metadata",
- 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"
+ action : "read",
+ source : arguments.mapping.getPath(),
+ query : "results.items",
+ properties : "results.metadata",
+ 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"
};
include "/coldbox/system/core/util/cffeed.cfm";
return results;
- }
+ }
// Internal DSL Builders
@@ -383,12 +430,12 @@
*
* @return The requested DSL object
*/
- function buildSimpleDSL( required dsl, required targetID, required targetObject = "" ){
- var definition = {
- required = true,
- name = "",
- dsl = arguments.dsl
- };
+ function buildSimpleDSL(
+ required dsl,
+ required targetID,
+ required targetObject = ""
+ ){
+ var definition = { required : true, name : "", dsl : arguments.dsl };
return buildDSLDependency(
definition = definition,
targetID = arguments.targetID,
@@ -434,115 +481,125 @@
*
* @return The requested object or null if not found and not required
*/
- function buildDSLDependency( required definition, required targetID, targetObject = "" ){
- var refLocal = {};
- var DSLNamespace = listFirst( arguments.definition.dsl, ":" );
+ 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 );
+ 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 ){
-
+ switch ( DSLNamespace ) {
// ColdBox Context DSL
- case "coldbox" : case "box" : {
- if( !variables.injector.isColdBoxLinked() ){
+ 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"
+ message = "The DSLNamespace: #DSLNamespace# cannot be used as it requires a ColdBox Context",
+ type = "Builder.IllegalDSLException"
);
}
- refLocal.dependency = variables.coldboxDSL.process( argumentCollection=arguments );
+ refLocal.dependency = variables.coldboxDSL.process( argumentCollection = arguments );
break;
}
// Executor
- case "executor" : {
+ case "executor": {
// retrieve it
- refLocal.dependency = getExecutorDSl( argumentCollection=arguments );
+ refLocal.dependency = getExecutorDSl( argumentCollection = arguments );
break;
}
// CacheBox Context DSL
- case "cacheBox" : {
+ case "cacheBox": {
// check if linked
- if( !variables.injector.isCacheBoxLinked() AND !variables.injector.isColdBoxLinked() ){
+ 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"
+ 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 );
+ refLocal.dependency = variables.cacheBoxDSL.process( argumentCollection = arguments );
break;
}
// logbox injection DSL always available
- case "logbox" : {
- refLocal.dependency = variables.logBoxDSL.process( argumentCollection=arguments );
+ 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 );
+ case "model":
+ case "id": {
+ refLocal.dependency = getModelDSL( argumentCollection = arguments );
break;
}
// provider injection DSL always available
- case "provider" : {
- refLocal.dependency = getProviderDSL( argumentCollection=arguments );
+ case "provider": {
+ refLocal.dependency = getProviderDSL( argumentCollection = arguments );
break;
}
// wirebox injection DSL always available
- case "wirebox" : {
- refLocal.dependency = getWireBoxDSL( argumentCollection=arguments );
+ case "wirebox": {
+ refLocal.dependency = getWireBoxDSL( argumentCollection = arguments );
break;
}
// java class
- case "java" : {
- refLocal.dependency = getJavaDSL( argumentCollection=arguments );
+ case "java": {
+ refLocal.dependency = getJavaDSL( argumentCollection = arguments );
break;
}
// coldfusion type annotation
- case "bytype" : {
- refLocal.dependency = getByTypeDSL( argumentCollection=arguments );
+ 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 );
+ 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( !isNull( refLocal.dependency ) ){
+ if ( !isNull( refLocal.dependency ) ) {
return refLocal.dependency;
}
// was dependency required? If so, then throw exception
- if( arguments.definition.required ){
-
+ 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#'" ); }
+ 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 ' )#";
+ var injectMessage = "The target '#arguments.targetID#' requested a missing dependency with a #depDesc.toList( " and " )#";
// Logging
- if( variables.log.canError() ){
+ if ( variables.log.canError() ) {
variables.log.error( injectMessage, arguments.definition );
}
@@ -550,16 +607,18 @@
throw(
message = injectMessage,
// safe serialization that won't blow uo on complex values or do weird things with nulls (looking at you, Adobe)
- detail = serializeJSON ( arguments.definition.map( function(k,v){
- if( isNull( v ) ) {
- return;
- } else if( !isSimpleValue( v ) ) {
- return '[complex value]';
- } else {
- return v;
- }
- } ) ),
- type = "Builder.DSLDependencyNotFoundException"
+ detail = serializeJSON(
+ arguments.definition.map( function( k, v ){
+ if ( isNull( v ) ) {
+ return;
+ } else if ( !isSimpleValue( v ) ) {
+ return "[complex value]";
+ } else {
+ return v;
+ }
+ } )
+ ),
+ type = "Builder.DSLDependencyNotFoundException"
);
}
// else return void, no dependency found that was required
@@ -574,7 +633,7 @@
* @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, ":" );
+ var javaClass = getToken( arguments.definition.dsl, 2, ":" );
return createObject( "java", javaClass );
}
@@ -586,41 +645,63 @@
* @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 = "";
+ var thisType = arguments.definition.dsl;
+ var thisTypeLen = listLen( thisType, ":" );
+ var thisLocationType = "";
+ var thisLocationKey = "";
// DSL stages
- switch( thisTypeLen ){
+ switch ( thisTypeLen ) {
// WireBox injector
- case 1 : { return variables.injector; }
+ case 1: {
+ return variables.injector;
+ }
// Level 2 DSL
- case 2 : {
+ 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(); }
+ switch ( thisLocationKey ) {
+ case "parent": {
+ return variables.injector.getParent();
+ }
+ case "eventManager": {
+ return variables.injector.getEventManager();
+ }
+ case "asyncManager": {
+ return variables.injector.getAsyncManager();
+ }
+ 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, ":" );
+ case 3: {
+ thisLocationType = getToken( thisType, 2, ":" );
+ thisLocationKey = getToken( thisType, 3, ":" );
// DSL Level 2 Stage Types
- switch( thisLocationType ){
+ switch ( thisLocationType ) {
// Scope DSL
- case "scope" : { return variables.injector.getScope( thisLocationKey ); break; }
- case "property" : { return variables.injector.getBinder().getProperty( thisLocationKey );break; }
+ case "scope": {
+ return variables.injector.getScope( thisLocationKey );
+ break;
+ }
+ case "property": {
+ return variables.injector.getBinder().getProperty( thisLocationKey );
+ break;
+ }
}
break;
- } // end level 3 main DSL
+ }
+ // end level 3 main DSL
}
}
@@ -631,30 +712,32 @@
* @targetObject The target object we are building the DSL dependency for
*/
private any function getExecutorDSl( required definition, targetObject ){
- var asyncManager = variables.injector.getAsyncManager();
- var thisType = arguments.definition.dsl;
- var thisTypeLen = listLen( thisType, ":" );
- var executorName = "";
+ var asyncManager = variables.injector.getAsyncManager();
+ var thisType = arguments.definition.dsl;
+ var thisTypeLen = listLen( thisType, ":" );
+ var executorName = "";
// DSL stages
- switch( thisTypeLen ){
+ switch ( thisTypeLen ) {
// property name='myExecutorService' inject="executor";
- case 1 : {
+ case 1: {
executorName = arguments.definition.name;
break;
}
// executor:{alias} stage
- case 2 : {
+ case 2: {
executorName = getToken( thisType, 2, ":" );
break;
}
}
// Check if executor Exists
- if( asyncManager.hasExecutor( executorName ) ){
+ if ( asyncManager.hasExecutor( executorName ) ) {
return asyncManager.getExecutor( executorName );
- } else if ( variables.log.canDebug() ){
- variables.log.debug( "X getExecutorDsl() cannot find executor #executorName# using definition #arguments.definition.toString()#" );
+ } else if ( variables.log.canDebug() ) {
+ variables.log.debug(
+ "X getExecutorDsl() cannot find executor #executorName# using definition #arguments.definition.toString()#"
+ );
}
}
@@ -665,15 +748,15 @@
* @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 = "";
+ var thisType = arguments.definition.dsl;
+ var thisTypeLen = listLen( thisType, ":" );
+ var methodCall = "";
+ var modelName = "";
// DSL stages
- switch( thisTypeLen ){
+ switch ( thisTypeLen ) {
// No injection defined, use property name: property name='luis' inject;
- case 0 : {
+ case 0: {
modelName = arguments.definition.name;
break;
}
@@ -681,9 +764,9 @@
// property name='luis' inject="id"; use property name
// property name='luis' inject="model"; use property name
// property name='luis' inject="alias";
- case 1 : {
+ case 1: {
// Are we the key identifiers
- if( listFindNoCase( "id,model", arguments.definition.dsl ) ){
+ if ( listFindNoCase( "id,model", arguments.definition.dsl ) ) {
modelName = arguments.definition.name;
}
// else we are a real ID
@@ -694,29 +777,31 @@
break;
}
// model:{alias} stage
- case 2 : {
+ case 2: {
modelName = getToken( thisType, 2, ":" );
break;
}
// model:{alias}:{method} stage
- case 3 : {
- modelName = getToken( thisType, 2, ":" );
- methodCall = getToken( thisType, 3, ":" );
+ case 3: {
+ modelName = getToken( thisType, 2, ":" );
+ methodCall = getToken( thisType, 3, ":" );
break;
}
}
// Check if model Exists
- if( variables.injector.containsInstance( modelName ) ){
+ 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 ) ){
+ 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()#" );
+ } else if ( variables.log.canDebug() ) {
+ variables.log.debug(
+ "getModelDSL() cannot find model object #modelName# using definition #arguments.definition.toString()#"
+ );
}
}
@@ -726,41 +811,47 @@
* @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 = "";
+ private any function getProviderDSL( required definition, targetObject = "" ){
+ var thisType = arguments.definition.dsl;
+ var thisTypeLen = listLen( thisType, ":" );
+ var providerName = "";
// DSL stages
- switch( thisTypeLen ){
+ switch ( thisTypeLen ) {
// provider default, get name of the provider from property
- case 1: { providerName = arguments.definition.name; break; }
+ case 1: {
+ providerName = arguments.definition.name;
+ break;
+ }
// provider:{name} stage
- case 2: { providerName = getToken( thisType, 2, ":" ); break; }
+ case 2: {
+ providerName = getToken( thisType, 2, ":" );
+ break;
+ }
// multiple stages then most likely it is a full DSL being used
- default : {
+ default: {
providerName = replaceNoCase( thisType, "provider:", "" );
}
}
// Build provider arguments
var args = {
- scopeRegistration = variables.injector.getScopeRegistration(),
- scopeStorage = variables.injector.getScopeStorage(),
- targetObject = arguments.targetObject
+ 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 ) ){
+ if ( variables.injector.containsInstance( providerName ) ) {
args.name = providerName;
}
// Else try to tag it by FULL DSL
- else{
+ else {
args.dsl = providerName;
}
// Build provider and return it.
- return createObject( "component","coldbox.system.ioc.Provider" ).init( argumentCollection=args );
+ return createObject( "component", "coldbox.system.ioc.Provider" ).init( argumentCollection = args );
}
/**
@@ -770,9 +861,9 @@
* @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;
+ var injectType = arguments.definition.type;
- if( variables.injector.containsInstance( injectType ) ){
+ if ( variables.injector.containsInstance( injectType ) ) {
return variables.injector.getInstance( injectType );
}
}
@@ -786,83 +877,98 @@
*
* @return The target object
*/
- function toVirtualInheritance( required mapping, required target, required targetMapping ){
- var excludedProperties = "$super,$wbaopmixed,$mixed,this,init";
+ function toVirtualInheritance(
+ required mapping,
+ required target,
+ required targetMapping
+ ){
+ var excludedProperties = "$super,super,$wbaopmixed,$mixed,this,init";
// Check if the base mapping has been discovered yet
- if( NOT arguments.mapping.isDiscovered() ){
+ if ( NOT arguments.mapping.isDiscovered() ) {
// process inspection of instance
- arguments.mapping.process(
- binder = variables.injector.getBinder(),
- injector = variables.injector
- );
+ arguments.mapping.process( binder = variables.injector.getBinder(), injector = variables.injector );
}
- // Build it out now and wire it
+ // Build it out the base object and wire it
var baseObject = variables.injector.buildInstance( arguments.mapping );
- variables.injector.autowire( target=baseObject, mapping=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" ) ){
+ // Check if init already exists in target and base? If so, then inject it as $superInit
+ 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 ){
+ for ( var key in baseObject ) {
// If target has overridden method, then don't override it with mixin, simulated inheritance
- if( NOT structKeyExists( arguments.target, key ) AND NOT listFindNoCase( excludedProperties, key ) ){
+ 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 ){
+ // 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 = {};
+ var baseProperties = {};
// Process baseProperties lookup map
- if( arguments.mapping.getObjectMetadata().keyExists( "properties" ) ){
- arguments.mapping.getObjectMetadata().properties
+ if ( arguments.mapping.getObjectMetadata().keyExists( "properties" ) ) {
+ arguments.mapping
+ .getObjectMetadata()
+ .properties
.each( function( item ){
baseProperties[ item.name ] = true;
} );
}
+ // Commenting out for now, as I believe this causes double initializations of objects
// 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 );
- }
+ // 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()
+ baseObject
+ .getVariablesMixin()
// filter out overrides
- .filter( function( key, value ) {
- return ( !targetVariables.keyExists( key ) AND NOT listFindNoCase( excludedProperties, key ) );
+ .filter( function( key, value ){
+ // If it's a function and not excluded and not overriden, then inject it
+ if (
+ isCustomFunction( arguments.value ) AND
+ !listFindNoCase( excludedProperties, arguments.key ) AND
+ !targetVariables.keyExists( arguments.key )
+ ) {
+ return true;
+ }
+
+ // Check for just data now
+ return ( !listFindNoCase( excludedProperties, arguments.key ) );
} )
.each( function( propertyName, propertyValue ){
// inject the property/method now
- if( !isNull( arguments.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( args.target, "get#propertyName#" ) ){
+ if ( generateAccessors and baseProperties.keyExists( propertyName ) ) {
+ if ( !structKeyExists( args.target, "get#propertyName#" ) ) {
args.target.injectMixin( "get" & propertyName, variables.genericGetter );
}
- if( ! structKeyExists( args.target, "set#propertyName#" ) ){
+ if ( !structKeyExists( args.target, "set#propertyName#" ) ) {
args.target.injectMixin( "set" & propertyName, variables.genericSetter );
}
-
}
} );
@@ -875,8 +981,8 @@
/**
* Generic setter for Virtual Inheritance
*/
- private function genericSetter() {
- var propName = getFunctionCalledName().replaceNoCase( 'set', '' );
+ private function genericSetter(){
+ var propName = getFunctionCalledName().replaceNoCase( "set", "" );
variables[ propName ] = arguments[ 1 ];
return this;
}
@@ -884,8 +990,8 @@
/**
* Generic getter for Virtual Inheritance
*/
- private function genericGetter() {
- var propName = getFunctionCalledName().replaceNoCase( 'get', '' );
+ private function genericGetter(){
+ var propName = getFunctionCalledName().replaceNoCase( "get", "" );
return variables[ propName ];
}
diff --git a/system/ioc/Injector.cfc b/system/ioc/Injector.cfc
index 28716be14..b3cab9321 100644
--- a/system/ioc/Injector.cfc
+++ b/system/ioc/Injector.cfc
@@ -23,7 +23,11 @@
* injector = new coldbox.system.ioc.Injector( "config.MyBinder" );
*
*/
-component serializable="false" accessors="true" implements="coldbox.system.ioc.IInjector"{
+component
+ serializable="false"
+ accessors ="true"
+ implements ="coldbox.system.ioc.IInjector"
+{
/**
* Java System
@@ -103,7 +107,7 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
/**
* The logBox task scheduler executor
- * @see coldbox.system.async.tasks.ScheduledExecutor
+ * @see coldbox.system.async.executors.ScheduledExecutor
*/
property name="taskScheduler";
@@ -117,22 +121,22 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
* @coldbox.doc_generic coldbox.system.web.Controller
**/
Injector function init(
- binder="coldbox.system.ioc.config.DefaultBinder",
- struct properties=structNew(),
- coldbox=""
+ binder = "coldbox.system.ioc.config.DefaultBinder",
+ struct properties = structNew(),
+ coldbox = ""
){
// Setup Available public scopes
this.SCOPES = new coldbox.system.ioc.Scopes();
// Setup Available public types
- this.TYPES = new coldbox.system.ioc.Types();
+ this.TYPES = new coldbox.system.ioc.Types();
// Do we have a binder?
- if( isSimpleValue( arguments.binder ) AND NOT len( trim( arguments.binder ) ) ){
+ if ( isSimpleValue( arguments.binder ) AND NOT len( trim( arguments.binder ) ) ) {
arguments.binder = "coldbox.system.ioc.config.DefaultBinder";
}
// Java System
- variables.javaSystem = createObject( 'java', 'java.lang.System' );
+ variables.javaSystem = createObject( "java", "java.lang.System" );
// Utility class
variables.utility = new coldbox.system.core.util.Util();
// Scope Storages
@@ -151,38 +155,41 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
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
+ "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 = "";
+ variables.logBox = "";
+ variables.log = "";
// Parent Injector
- variables.parent = "";
+ variables.parent = "";
// LifeCycle Scopes
- variables.scopes = {};
+ variables.scopes = {};
// Prepare instance ID
variables.injectorID = variables.javaSystem.identityHashCode( this );
// Prepare Lock Info
- variables.lockName = "WireBox.Injector.#variables.injectorID#";
+ variables.lockName = "WireBox.Injector.#variables.injectorID#";
// Link ColdBox Context if passed
- variables.coldbox = arguments.coldbox;
+ variables.coldbox = arguments.coldbox;
// Register the task scheduler according to operating mode
- if( !isObject( variables.coldbox ) ){
- variables.asyncManager = new coldbox.system.async.AsyncManager();
- variables.taskScheduler = variables.asyncManager.newScheduledExecutor( name : "wirebox-tasks", threads : 20 );
+ if ( !isObject( variables.coldbox ) ) {
+ variables.asyncManager = new coldbox.system.async.AsyncManager();
+ variables.taskScheduler = variables.asyncManager.newScheduledExecutor(
+ name : "wirebox-tasks",
+ threads: 20
+ );
} else {
- variables.asyncManager = variables.coldbox.getAsyncManager();
+ variables.asyncManager = variables.coldbox.getAsyncManager();
variables.taskScheduler = variables.asyncManager.getExecutor( "coldbox-tasks" );
}
@@ -201,18 +208,18 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
* @properties.doc_generic struct
**/
Injector function configure( required binder, required struct properties ){
- var iData = {};
- var withColdbox = isColdBoxLinked();
+ var iData = {};
+ var withColdbox = isColdBoxLinked();
// Lock For Configuration
- lock name=variables.lockName type="exclusive" timeout="30" throwontimeout="true"{
- if( withColdBox ){
+ lock name=variables.lockName type="exclusive" timeout="30" throwontimeout="true" {
+ if ( withColdBox ) {
// link LogBox
- variables.logBox = variables.coldbox.getLogBox();
+ variables.logBox = variables.coldbox.getLogBox();
// Configure Logging for this injector
- variables.log = variables.logBox.getLogger( this );
+ variables.log = variables.logBox.getLogger( this );
// Link CacheBox
- variables.cacheBox = variables.coldbox.getCacheBox();
+ variables.cacheBox = variables.coldbox.getCacheBox();
// Link Event Manager
variables.eventManager = variables.coldbox.getInterceptorService();
}
@@ -224,7 +231,7 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
variables.binder = buildBinder( arguments.binder, arguments.properties );
// Create local cache, logging and event management if not coldbox context linked.
- if( NOT withColdbox ){
+ if ( NOT withColdbox ) {
// Running standalone, so create our own logging first
configureLogBox( variables.binder.getLogBoxConfig() );
// Create local CacheBox reference
@@ -240,24 +247,27 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
// Register Life Cycle Scopes
registerScopes();
// Parent Injector declared
- if( isObject( variables.binder.getParentInjector() ) ){
+ if ( isObject( variables.binder.getParentInjector() ) ) {
setParent( variables.binder.getParentInjector() );
}
// Scope registration if enabled?
- if( variables.binder.getScopeRegistration().enabled ){
+ if ( variables.binder.getScopeRegistration().enabled ) {
doScopeRegistration();
}
// Register binder as an interceptor
- if( NOT isColdBoxLinked() ){
+ if ( NOT isColdBoxLinked() ) {
variables.eventManager.register( variables.binder, "wirebox-binder" );
} else {
- variables.eventManager.registerInterceptor( interceptorObject=variables.binder, interceptorName="wirebox-binder" );
+ variables.eventManager.registerInterceptor(
+ interceptorObject = variables.binder,
+ interceptorName = "wirebox-binder"
+ );
}
// process mappings for metadata and initialization.
- if( variables.binder.getAutoProcessMappings() ){
+ if ( variables.binder.getAutoProcessMappings() ) {
variables.binder.processMappings();
}
@@ -265,7 +275,7 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
variables.binder.processEagerInits();
// Check if binder has onLoad convention and execute callback
- if( structKeyExists( variables.binder, "onLoad" ) ){
+ if ( structKeyExists( variables.binder, "onLoad" ) ) {
variables.binder.onLoad( this );
}
@@ -281,12 +291,10 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
* Shutdown the injector gracefully by calling the shutdown events internally.
**/
function shutdown(){
- var iData = {
- injector = this
- };
+ var iData = { injector : this };
// Log
- if( variables.log.canInfo() ){
+ if ( variables.log.canInfo() ) {
variables.log.info( "Shutdown of Injector: #getInjectorID()# requested and started." );
}
@@ -294,17 +302,17 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
variables.eventManager.announce( "beforeInjectorShutdown", iData );
// Check if binder has onShutdown convention
- if( structKeyExists( variables.binder, "onShutdown" ) ){
+ if ( structKeyExists( variables.binder, "onShutdown" ) ) {
variables.binder.onShutdown( this );
}
// Is parent linked
- if( isObject( variables.parent ) ){
+ if ( isObject( variables.parent ) ) {
variables.parent.shutdown( this );
}
// standalone cachebox? Yes, then shut it down baby!
- if( isCacheBoxLinked() ){
+ if ( isCacheBoxLinked() ) {
variables.cacheBox.shutdown( this );
}
@@ -315,18 +323,18 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
variables.eventManager.announce( "afterInjectorShutdown", iData );
// Log shutdown complete
- if( variables.log.canInfo() ){
+ if ( variables.log.canInfo() ) {
variables.log.info( "Shutdown of injector: #getInjectorID()# completed." );
}
// Shutdown LogBox last if not in ColdBox Mode
- if( !isColdBoxLinked() ){
+ if ( !isColdBoxLinked() ) {
variables.logBox.shutdown();
}
// Shutdown Executors if not in ColdBox Mode
// This needs to happen AFTER logbox is shutdown since they share the taskScheduler
- if( !isColdBoxLinked() ){
+ if ( !isColdBoxLinked() ) {
variables.asyncManager.shutdownAllExecutors( force = true );
}
@@ -345,15 +353,19 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
*
* @return The requested instance
**/
- function getInstance( name, struct initArguments = {}, dsl, targetObject="" ){
-
+ function getInstance(
+ name,
+ struct initArguments = {},
+ dsl,
+ targetObject = ""
+ ){
// Is the name a DSL?
- if( !isNull( arguments.name ) && variables.builder.isDSLString( arguments.name ) ){
+ if ( !isNull( arguments.name ) && variables.builder.isDSLString( arguments.name ) ) {
arguments.dsl = arguments.name;
}
// Get by DSL?
- if( !isNull( arguments.dsl ) ){
+ if ( !isNull( arguments.dsl ) ) {
return variables.builder.buildSimpleDSL(
dsl = arguments.dsl,
targetID = "ExplicitCall",
@@ -362,22 +374,24 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
}
// Check if Mapping Exists?
- if( NOT variables.binder.mappingExists( arguments.name ) ){
+ 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 ) ){
+ 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 );
+ 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" );
+ 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",
+ detail = "The instance could not be located in any declared scan location(s) (#structKeyList( variables.binder.getScanLocations() )#) or full path location",
type = "Injector.InstanceNotFoundException"
);
}
@@ -389,37 +403,38 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
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() ){
+ if ( NOT mapping.isDiscovered() ) {
try {
// process inspection of instance
- mapping.process( binder=variables.binder, injector=this );
- } catch( any e ) {
+ 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 );
+ 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 )#" );
+ 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" );
+ 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 );
+ var target = variables.scopes[ mapping.getScope() ].getFromScope( mapping, arguments.initArguments );
// Announce creation, initialization and DI magicfinicitation!
variables.eventManager.announce(
"afterInstanceCreation",
- { mapping=mapping, target=target, injector=this }
+ { mapping : mapping, target : target, injector : this }
);
return target;
@@ -438,51 +453,51 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
// before construction event
variables.eventManager.announce(
"beforeInstanceCreation",
- { mapping=arguments.mapping, injector=this }
+ { mapping : arguments.mapping, injector : this }
);
- var oModel = "";
+ var oModel = "";
// determine construction type
- switch( thisMap.getType() ){
- case "cfc" : {
+ switch ( thisMap.getType() ) {
+ case "cfc": {
oModel = variables.builder.buildCFC( thisMap, arguments.initArguments );
break;
}
- case "java" : {
+ case "java": {
oModel = variables.builder.buildJavaClass( thisMap );
break;
}
- case "webservice" : {
+ case "webservice": {
oModel = variables.builder.buildWebservice( thisMap, arguments.initArguments );
break;
}
- case "constant" : {
+ case "constant": {
oModel = thisMap.getValue();
break;
}
- case "rss" : {
+ case "rss": {
oModel = variables.builder.buildFeed( thisMap );
break;
}
- case "dsl" : {
- oModel = variables.builder.buildSimpleDSL( dsl=thisMap.getDSL(), targetID=thisMap.getName() );
+ case "dsl": {
+ oModel = variables.builder.buildSimpleDSL( dsl = thisMap.getDSL(), targetID = thisMap.getName() );
break;
}
- case "factory" : {
+ case "factory": {
oModel = variables.builder.buildFactoryMethod( thisMap, arguments.initArguments );
break;
}
- case "provider" : {
+ case "provider": {
// verify if it is a simple value or closure/UDF
- if( isSimpleValue( thisMap.getPath() ) ){
+ if ( isSimpleValue( thisMap.getPath() ) ) {
oModel = getInstance( thisMap.getPath() ).$get();
} else {
var closure = thisMap.getPath();
- oModel = closure( injector = this );
+ oModel = closure( injector = this );
}
break;
}
- default : {
+ default: {
throw(
message = "Invalid Construction Type: #thisMap.getType()#",
type = "Injector.InvalidConstructionType"
@@ -492,24 +507,30 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
// Check and see if this mapping as an influence closure
var influenceClosure = thisMap.getInfluenceClosure();
- if( !isSimpleValue( influenceClosure ) ) {
+ if ( !isSimpleValue( influenceClosure ) ) {
// Influence the creation of the instance
- var result = influenceClosure( instance=oModel, injector=this );
+ var result = influenceClosure( instance = oModel, injector = this );
// Allow the closure to override the entire instance if it wishes
- if( !isNull( local.result ) ){
+ if ( !isNull( local.result ) ) {
oModel = result;
}
}
// log data
- if( variables.log.canDebug() ){
- variables.log.debug( "Instance object built: #arguments.mapping.getName()#:#arguments.mapping.getPath().toString()#" );
+ if ( variables.log.canDebug() ) {
+ variables.log.debug(
+ "Instance object built: #arguments.mapping.getName()#:#arguments.mapping.getPath().toString()#"
+ );
}
// announce afterInstanceInitialized
variables.eventManager.announce(
"afterInstanceInitialized",
- { mapping=arguments.mapping, target=oModel, injector=this }
+ {
+ mapping : arguments.mapping,
+ target : oModel,
+ injector : this
+ }
);
return oModel;
@@ -522,34 +543,35 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
* @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 coldbox.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;
- }
+ // 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 coldbox.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: coldbox.system.ioc.dsl.IDSLBuilder
- */
- Injector function registerDSL( required namespace, required path ){
- variables.builder.registerDSL( argumentCollection=arguments );
+ }
+
+ /**
+ * 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: coldbox.system.ioc.dsl.IDSLBuilder
+ */
+ Injector function registerDSL( required namespace, required path ){
+ variables.builder.registerDSL( argumentCollection = arguments );
return this;
}
@@ -560,15 +582,15 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
*/
boolean function containsInstance( required name ){
// check if we have a mapping first
- if( variables.binder.mappingExists( arguments.name ) ){
+ if ( variables.binder.mappingExists( arguments.name ) ) {
return true;
}
// check if we can locate it?
- if( locateInstance( arguments.name ).len() ){
+ if ( locateInstance( arguments.name ).len() ) {
return true;
}
// Ask parent hierarchy if set
- if( isObject( variables.parent ) ){
+ if ( isObject( variables.parent ) ) {
return variables.parent.containsInstance( arguments.name );
}
@@ -582,31 +604,37 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
* @name The model instance name to locate
*/
function locateInstance( required name ){
- var scanLocations = variables.binder.getScanLocations();
- var CFCName = replace( arguments.name, ".", "/", "all" ) & ".cfc";
+ 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 ) ){
+ if ( find( ":", CFCName ) ) {
return "";
}
// Check Scan Locations In Order
- for( var thisScanPath in scanLocations ){
+ 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#" ); }
+ 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." ); }
+ 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" ); }
+ if ( variables.log.canDebug() ) {
+ variables.log.debug( "Instance: #arguments.name# was not located anywhere" );
+ }
return "";
}
@@ -624,60 +652,68 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
function autowire(
required target,
mapping,
- targetID="",
- boolean annotationCheck=false
+ targetID = "",
+ boolean annotationCheck = false
){
- var targetObject = arguments.target;
- var md = "";
+ var targetObject = arguments.target;
+ var md = "";
// Do we have a mapping? Or is this a-la-carte wiring
- if( NOT structKeyExists( arguments, "mapping" ) ){
+ 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 ) ){
+ if ( NOT len( arguments.targetID ) ) {
// need to get metadata to verify identity
- md = variables.utility.getInheritedMetaData( arguments.target, getBinder().getStopRecursions() );
+ 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 ) ){
+ if ( NOT variables.binder.mappingExists( arguments.targetID ) ) {
// No mapping found, means we need to map this object for the first time.
// Is md retrieved? 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() );
+ 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 );
+ 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
+ }
+ // 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 ) ){
+ 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() ) )
- ){
-
+ 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
+ mapping : thisMap,
+ target : arguments.target,
+ targetID : arguments.targetID,
+ injector : this
};
variables.eventManager.announce( "beforeInstanceAutowire", iData );
@@ -685,20 +721,28 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
variables.utility.getMixerUtil().start( arguments.target );
// Bean Factory Awareness
- if( structKeyExists( targetObject, "setBeanFactory" ) ){
+ if ( structKeyExists( targetObject, "setBeanFactory" ) ) {
targetObject.setBeanFactory( this );
}
- if( structKeyExists( targetObject, "setInjector" ) ){
+ if ( structKeyExists( targetObject, "setInjector" ) ) {
targetObject.setInjector( this );
}
// ColdBox Context Awareness
- if( structKeyExists( targetObject, "setColdBox" ) ){
+ if ( structKeyExists( targetObject, "setColdBox" ) ) {
targetObject.setColdBox( getColdBox() );
}
// DIProperty injection
- processInjection( targetObject, thisMap.getDIProperties(), arguments.targetID );
+ processInjection(
+ targetObject,
+ thisMap.getDIProperties(),
+ arguments.targetID
+ );
// DISetter injection
- processInjection( targetObject, thisMap.getDISetters(), arguments.targetID );
+ processInjection(
+ targetObject,
+ thisMap.getDISetters(),
+ arguments.targetID
+ );
// Process Provider Methods
processProviderMethods( targetObject, thisMap );
// Process Mixins
@@ -710,8 +754,11 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
variables.eventManager.announce( "afterInstanceAutowire", iData );
// Debug Data
- if( variables.log.canDebug() ){
- variables.log.debug( "Finalized Autowire for: #arguments.targetID#", thisMap.getMemento().toString() );
+ if ( variables.log.canDebug() ) {
+ variables.log.debug(
+ "Finalized Autowire for: #arguments.targetID#",
+ thisMap.getMemento().toString()
+ );
}
}
}
@@ -734,7 +781,7 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
*
* @doc_generic coldbox.system.ioc.Injector
*/
- function getParent() {
+ function getParent(){
return variables.parent;
}
@@ -743,7 +790,7 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
*
* @doc_generic coldbox.system.core.dynamic.BeanPopulator
*/
- function getObjectPopulator() {
+ function getObjectPopulator(){
return new coldbox.system.core.dynamic.BeanPopulator();
}
@@ -752,7 +799,7 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
*
* @doc_generic boolean
*/
- boolean function isColdBoxLinked() {
+ boolean function isColdBoxLinked(){
return isObject( variables.coldbox );
}
@@ -761,21 +808,21 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
*
* @doc_generic boolean
*/
- boolean function isCacheBoxLinked() {
+ boolean function isCacheBoxLinked(){
return isObject( variables.cacheBox );
}
/**
* Remove the Injector from scope registration if enabled, else does nothing
*/
- Injector function removeFromScope() {
+ Injector function removeFromScope(){
var scopeInfo = variables.binder.getScopeRegistration();
// if enabled remove.
- if( scopeInfo.enabled ){
+ if ( scopeInfo.enabled ) {
variables.scopeStorage.delete( scopeInfo.key, scopeInfo.scope );
// Log info
- if( variables.log.canDebug() ){
+ if ( variables.log.canDebug() ) {
variables.log.debug( "Injector removed from scope: #scopeInfo.toString()#" );
}
}
@@ -793,7 +840,7 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
/**
* Clear the singleton cache
*/
- Injector function clearSingletons() {
+ Injector function clearSingletons(){
variables.scopes[ "SINGLETON" ].clear();
return this;
}
@@ -803,11 +850,11 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
*
* @doc_generic coldbox.system.ioc.Injector
*/
- function locateScopedSelf() {
+ function locateScopedSelf(){
var scopeInfo = variables.binder.getScopeRegistration();
// Return if it exists, else throw exception
- if( variables.scopeStorage.exists( scopeInfo.key, scopeInfo.scope ) ){
+ if ( variables.scopeStorage.exists( scopeInfo.key, scopeInfo.scope ) ) {
return variables.scopeStorage.get( scopeInfo.key, scopeInfo.scope );
}
@@ -823,7 +870,7 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
*
* @return coldbox.system.core.util.Util
*/
- function getUtil() {
+ function getUtil(){
return variables.utility;
}
@@ -844,7 +891,7 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
*/
private Injector function processMixins( required targetObject, required mapping ){
// If no length, kick out
- if( !arguments.mapping.getMixins().len() ){
+ if ( !arguments.mapping.getMixins().len() ) {
return this;
}
@@ -852,10 +899,10 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
var mixin = new coldbox.system.ioc.config.Mixin().$init( arguments.mapping.getMixins() );
// iterate and mixin baby!
- for( var key in mixin ){
- if( key NEQ "$init" ){
+ 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 ] );
+ arguments.targetObject.injectMixin( name = key, UDF = mixin[ key ] );
}
}
@@ -870,18 +917,18 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
*/
private Injector function processProviderMethods( required targetObject, required mapping ){
var providerMethods = arguments.mapping.getProviderMethods();
- var providerLen = arrayLen( providerMethods );
- var x = 1;
+ 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 = {};
+ 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++ ){
+ 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
@@ -896,20 +943,20 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
* @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) {
+ private Injector function processAfterCompleteDI( required targetObject, required DICompleteMethods ){
var DILen = arrayLen( arguments.DICompleteMethods );
// Check for convention first
- if ( StructKeyExists( arguments.targetObject, "onDIComplete" ) ) {
+ if ( structKeyExists( arguments.targetObject, "onDIComplete" ) ) {
// Call our mixin invoker
- arguments.targetObject.invokerMixin( method="onDIComplete" );
+ arguments.targetObject.invokerMixin( method = "onDIComplete" );
}
// Iterate on DICompleteMethods
- for( var thisMethod in arguments.DICompleteMethods ) {
- if ( StructKeyExists( arguments.targetObject, thisMethod ) ) {
+ for ( var thisMethod in arguments.DICompleteMethods ) {
+ if ( structKeyExists( arguments.targetObject, thisMethod ) ) {
// Call our mixin invoker
- arguments.targetObject.invokerMixin( method=thisMethod );
+ arguments.targetObject.invokerMixin( method = thisMethod );
}
}
@@ -923,19 +970,23 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
* @DIData The DI data to use
* @targetID The target ID to process injections
*/
- private Injector function processInjection( required targetObject, required DIData, required targetID ){
+ private Injector function processInjection(
+ required targetObject,
+ required DIData,
+ required targetID
+ ){
var DILen = arrayLen( arguments.DIData );
- for( var x=1; x lte DILen; x++ ){
+ for ( var x = 1; x lte DILen; x++ ) {
var thisDIData = arguments.DIData[ x ];
// Init the lookup structure
- var refLocal = {};
+ var refLocal = {};
// Check if direct value has been placed.
- if( !isNull( thisDIData.value ) ){
+ if ( !isNull( thisDIData.value ) ) {
refLocal.dependency = thisDIData.value;
}
// else check if dsl is used?
- else if( !isNull( thisDIData.dsl ) ){
+ else if ( !isNull( thisDIData.dsl ) ) {
// Get DSL dependency by sending entire DI structure to retrieve
refLocal.dependency = variables.builder.buildDSLDependency(
definition = thisDIData,
@@ -944,15 +995,15 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
);
}
// else we have to have a reference ID or a nasty bug has occurred
- else{
+ else {
refLocal.dependency = getInstance( arguments.DIData[ x ].ref );
}
// Check if dependency located, else log it and skip
- if( structKeyExists( refLocal, "dependency" ) ){
+ if ( structKeyExists( refLocal, "dependency" ) ) {
// scope or setter determination
refLocal.scope = "";
- if( structKeyExists( arguments.DIData[ x ], "scope" ) ){
+ if ( structKeyExists( arguments.DIData[ x ], "scope" ) ) {
refLocal.scope = arguments.DIData[ x ].scope;
}
// Inject dependency
@@ -965,14 +1016,18 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
);
// some debugging goodness
- if( variables.log.canDebug() ){
- variables.log.debug( "Dependency: #arguments.DIData[ x ].toString()# --> injected into #arguments.targetID#" );
+ 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() )#"
+ );
}
- 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
+ }
+ // end iteration
return this;
}
@@ -993,12 +1048,12 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
required scope,
required argName
){
- var argCollection = {};
+ var argCollection = {};
argCollection[ arguments.argName ] = arguments.propertyObject;
// Property or Setter
- if ( len( arguments.scope ) == 0 ){
+ if ( len( arguments.scope ) == 0 ) {
// Call our mixin invoker: setterMethod
- arguments.target.invokerMixin( method="set#arguments.propertyName#", argCollection=argCollection );
+ arguments.target.invokerMixin( method = "set#arguments.propertyName#", argCollection = argCollection );
} else {
// Call our property injector mixin
arguments.target.injectPropertyMixin(
@@ -1014,33 +1069,35 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
/**
* Register all internal and configured WireBox Scopes
*/
- private Injector function registerScopes() {
- var customScopes = variables.binder.getCustomScopes();
+ private Injector function registerScopes(){
+ var customScopes = variables.binder.getCustomScopes();
// register no_scope
- variables.scopes[ "NOSCOPE" ] = new coldbox.system.ioc.scopes.NoScope( this );
+ variables.scopes[ "NOSCOPE" ] = new coldbox.system.ioc.scopes.NoScope( this );
// register singleton
- variables.scopes[ "SINGLETON" ] = new coldbox.system.ioc.scopes.Singleton( this );
+ variables.scopes[ "SINGLETON" ] = new coldbox.system.ioc.scopes.Singleton( this );
// is cachebox linked?
- if( isCacheBoxLinked() ){
+ if ( isCacheBoxLinked() ) {
variables.scopes[ "CACHEBOX" ] = new coldbox.system.ioc.scopes.CacheBox( this );
}
// CF Scopes and references
- variables.scopes[ "REQUEST" ] = new coldbox.system.ioc.scopes.RequestScope( this );
- variables.scopes[ "SESSION" ] = new coldbox.system.ioc.scopes.CFScopes( this );
- variables.scopes[ "SERVER" ] = variables.scopes[ "SESSION" ];
- variables.scopes[ "APPLICATION" ] = variables.scopes[ "SESSION" ];
+ variables.scopes[ "REQUEST" ] = new coldbox.system.ioc.scopes.RequestScope( this );
+ variables.scopes[ "SESSION" ] = new coldbox.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 )#" );
+ if ( variables.log.canDebug() ) {
+ variables.log.debug(
+ "Registered all internal lifecycle scopes successfully: #structKeyList( variables.scopes )#"
+ );
}
// Register Custom Scopes
- for( var key in customScopes ){
+ for ( var key in customScopes ) {
variables.scopes[ key ] = createObject( "component", customScopes[ key ] ).init( this );
// Debugging
- if( variables.log.canDebug() ){
+ if ( variables.log.canDebug() ) {
variables.log.debug( "Registered custom scope: #key# (#customScopes[ key ]#)" );
}
}
@@ -1051,12 +1108,12 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
/**
* Register all the configured listeners in the configuration file
*/
- private Injector function registerListeners() {
- var listeners = variables.binder.getListeners();
- var regLen = arrayLen( listeners );
+ 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++ ){
+ for ( var x = 1; x lte regLen; x++ ) {
registerListener( listeners[ x ] );
}
return this;
@@ -1068,12 +1125,12 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
* @listener The listener to register
*/
public Injector function registerListener( required listener ){
- try{
+ try {
// create it
var thisListener = createObject( "component", listener.class );
// configure it
thisListener.configure( this, listener.properties );
- } catch( Any e ) {
+ } catch ( Any e ) {
variables.log.error( "Error creating listener: #listener.toString()#", e );
throw(
message = "Error creating listener: #listener.toString()#",
@@ -1083,14 +1140,17 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
}
// Now register listener
- if( NOT isColdBoxLinked() ){
+ if ( NOT isColdBoxLinked() ) {
variables.eventManager.register( thisListener, listener.name );
} else {
- variables.eventManager.registerInterceptor( interceptorObject=thisListener, interceptorName=listener.name );
+ variables.eventManager.registerInterceptor(
+ interceptorObject = thisListener,
+ interceptorName = listener.name
+ );
}
// debugging
- if( variables.log.canDebug() ){
+ if ( variables.log.canDebug() ) {
variables.log.debug( "Injector has just registered a new listener: #listener.toString()#" );
}
@@ -1100,14 +1160,14 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
/**
* Register this injector on a user specified scope
*/
- private Injector function doScopeRegistration() {
+ 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() ){
+ if ( variables.log.canDebug() ) {
variables.log.debug( "Scope Registration enabled and Injector scoped to: #scopeInfo.toString()#" );
}
@@ -1121,27 +1181,29 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
* @config.doc_generic struct
*/
private Injector function configureCacheBox( required struct config ){
- var args = {};
+ var args = {};
// is cachebox enabled?
- if( NOT arguments.config.enabled ){
+ if ( NOT arguments.config.enabled ) {
return this;
}
// Do we have a cacheBox reference?
- if( isObject( arguments.config.cacheFactory ) ){
+ 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()#" );
+ 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 ) ){
+ if ( len( arguments.config.configFile ) ) {
// xml?
- if( listFindNoCase( "xml,cfm", listLast( arguments.config.configFile, "." ) ) ){
+ if ( listFindNoCase( "xml,cfm", listLast( arguments.config.configFile, "." ) ) ) {
args[ "XMLConfig" ] = arguments.config.configFile;
} else {
// cfc
@@ -1149,11 +1211,18 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
}
// Create CacheBox
- var oConfig = createObject( "component", "#arguments.config.classNamespace#.config.CacheBoxConfig" ).init( argumentCollection=args );
- variables.cacheBox = createObject( "component", "#arguments.config.classNamespace#.CacheFactory" ).init( config=oConfig, wirebox=this );
+ var oConfig = createObject( "component", "#arguments.config.classNamespace#.config.CacheBoxConfig" ).init(
+ argumentCollection = args
+ );
+ variables.cacheBox = createObject( "component", "#arguments.config.classNamespace#.CacheFactory" ).init(
+ config = oConfig,
+ wirebox = this
+ );
// debugging
- if( variables.log.canDebug() ){
- variables.log.debug( "Configured Injector #getInjectorID()# with CacheBox instance: #variables.cacheBox.getFactoryID()# and configuration file: #arguments.config.configFile#" );
+ if ( variables.log.canDebug() ) {
+ variables.log.debug(
+ "Configured Injector #getInjectorID()# with CacheBox instance: #variables.cacheBox.getFactoryID()# and configuration file: #arguments.config.configFile#"
+ );
}
return this;
}
@@ -1161,8 +1230,10 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
// 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()#" );
+ if ( variables.log.canDebug() ) {
+ variables.log.debug(
+ "Configured Injector #getInjectorID()# with vanilla CacheBox instance: #variables.cacheBox.getFactoryID()#"
+ );
}
return this;
@@ -1172,22 +1243,22 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
* Configure a standalone version of logBox for logging
*/
private Injector function configureLogBox( required configPath ){
- var args = structnew();
+ var args = structNew();
// xml?
- if( listFindNoCase( "xml,cfm", listLast( arguments.configPath, "." ) ) ){
+ if ( listFindNoCase( "xml,cfm", listLast( arguments.configPath, "." ) ) ) {
args[ "XMLConfig" ] = arguments.configPath;
} else {
// cfc
args[ "CFCConfigPath" ] = arguments.configPath;
}
- var config = new coldbox.system.logging.config.LogBoxConfig( argumentCollection=args );
+ var config = new coldbox.system.logging.config.LogBoxConfig( argumentCollection = args );
// Create LogBox
- variables.logBox = new coldbox.system.logging.LogBox( config=config, wirebox=this );
+ variables.logBox = new coldbox.system.logging.LogBox( config = config, wirebox = this );
// Configure Logging for this injector
- variables.log = variables.logBox.getLogger( this );
+ variables.log = variables.logBox.getLogger( this );
return this;
}
@@ -1195,9 +1266,9 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
/**
* Configure a standalone version of a WireBox Event Manager
*/
- private Injector function configureEventManager() {
+ private Injector function configureEventManager(){
// Use or create event manager
- if( isColdBoxLinked() && isObject( variables.eventManager ) ){
+ if ( isColdBoxLinked() && isObject( variables.eventManager ) ) {
// Link Interception States
variables.eventManager.appendInterceptionPoints( variables.eventStates );
return this;
@@ -1217,7 +1288,7 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
*/
private any function buildBinder( required binder, required properties ){
// Check if just a plain CFC path and build it
- if( isSimpleValue( arguments.binder ) ){
+ if ( isSimpleValue( arguments.binder ) ) {
arguments.binder = createObject( "component", arguments.binder );
}
@@ -1227,7 +1298,7 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
arguments.binder[ "getEnv" ] = variables.utility.getEnv;
// Check if data CFC or binder family
- if( NOT isInstanceOf( arguments.binder, "coldbox.system.ioc.config.Binder" ) ){
+ if ( NOT isInstanceOf( arguments.binder, "coldbox.system.ioc.config.Binder" ) ) {
// simple data cfc, create native binder and decorate data CFC
var nativeBinder = new coldbox.system.ioc.config.Binder(
injector = this,
@@ -1236,7 +1307,7 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
);
} else {
// else init the binder and configure it
- var nativeBinder = arguments.binder.init( injector=this, properties=arguments.properties );
+ var nativeBinder = arguments.binder.init( injector = this, properties = arguments.properties );
// Configure it
nativeBinder.configure();
// Load it
@@ -1246,4 +1317,4 @@ component serializable="false" accessors="true" implements="coldbox.system.ioc.I
return nativeBinder;
}
-}
\ No newline at end of file
+}
diff --git a/system/ioc/dsl/ColdBoxDSL.cfc b/system/ioc/dsl/ColdBoxDSL.cfc
index 796a5e4d2..21898888d 100644
--- a/system/ioc/dsl/ColdBoxDSL.cfc
+++ b/system/ioc/dsl/ColdBoxDSL.cfc
@@ -1,10 +1,10 @@
/**
-* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp
-* www.ortussolutions.com
-* ---
-* Process DSL functions via ColdBox
-**/
-component implements="coldbox.system.ioc.dsl.IDSLBuilder" accessors="true"{
+ * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp
+ * www.ortussolutions.com
+ * ---
+ * Process DSL functions via ColdBox
+ **/
+component implements="coldbox.system.ioc.dsl.IDSLBuilder" accessors="true" {
/**
* Injector Reference
@@ -35,10 +35,10 @@ component implements="coldbox.system.ioc.dsl.IDSLBuilder" accessors="true"{
* @return coldbox.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 );
+ variables.injector = arguments.injector;
+ variables.coldbox = variables.injector.getColdBox();
+ variables.cacheBox = variables.injector.getCacheBox();
+ variables.log = variables.injector.getLogBox().getLogger( this );
return this;
}
@@ -54,8 +54,11 @@ component implements="coldbox.system.ioc.dsl.IDSLBuilder" accessors="true"{
function process( required definition, targetObject ){
var DSLNamespace = listFirst( arguments.definition.dsl, ":" );
- switch( DSLNamespace ){
- case "coldbox" : case "box" : { return getColdboxDSL( argumentCollection=arguments ); }
+ switch ( DSLNamespace ) {
+ case "coldbox":
+ case "box": {
+ return getColdboxDSL( argumentCollection = arguments );
+ }
}
// else ignore not our DSL
@@ -70,141 +73,187 @@ component implements="coldbox.system.ioc.dsl.IDSLBuilder" accessors="true"{
* @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 = "";
+ 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
+ if (
+ thisTypeLen eq 2
and listFindNoCase( "setting,fwSetting,interceptor", listLast( thisType, ":" ) )
and len( thisName )
- ){
+ ) {
// Add the additional alias to the DSL
- thisType = thisType & ":" & thisName;
+ thisType = thisType & ":" & thisName;
thisTypeLen = 3;
}
// DSL stages
- switch( thisTypeLen ){
+ switch ( thisTypeLen ) {
// coldbox only DSL
- case 1 : {
+ case 1: {
return variables.coldbox;
}
// coldbox:{key} stage 2
- case 2 : {
+ case 2: {
thisLocationKey = getToken( thisType, 2, ":" );
- switch( thisLocationKey ){
+ switch ( thisLocationKey ) {
// Config Struct
- case "configSettings" : { return variables.coldbox.getConfigSettings(); }
- case "dataMarshaller" : { return variables.coldbox.getDataMarshaller(); }
- case "flash" : { return variables.coldbox.getRequestService().getFlashScope(); }
- case "fwSettings" : case "coldboxSettings" : { 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(); }
- case "moduleconfig" : { return variables.coldbox.getSetting( "modules" ); }
- case "asyncManager" : { return variables.coldbox.getAsyncManager(); }
- } // end of services
+ case "configSettings": {
+ return variables.coldbox.getConfigSettings();
+ }
+ case "dataMarshaller": {
+ return variables.coldbox.getDataMarshaller();
+ }
+ case "flash": {
+ return variables.coldbox.getRequestService().getFlashScope();
+ }
+ case "fwSettings":
+ case "coldboxSettings": {
+ 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();
+ }
+ case "moduleconfig": {
+ return variables.coldbox.getSetting( "modules" );
+ }
+ case "asyncManager": {
+ return variables.coldbox.getAsyncManager();
+ }
+ case "appScheduler": {
+ return variables.injector.getInstance( "appScheduler@coldbox" );
+ }
+ }
+ // end of services
break;
}
- //coldbox:{key}:{target} Usually for named factories
- case 3 : {
+ // coldbox:{key}:{target} Usually for named factories
+ case 3: {
thisLocationType = getToken( thisType, 2, ":" );
thisLocationKey = getToken( thisType, 3, ":" );
- switch( thisLocationType ){
- case "setting" : case "configSettings" : {
+ switch ( thisLocationType ) {
+ case "setting":
+ case "configSettings": {
// module setting?
- if( find( "@", thisLocationKey ) ){
+ 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 (
+ 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 {
throw(
- type = "ColdBoxDSL.InvalidDSL",
+ 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)#"
+ detail = "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" : {
+ case "modulesettings": {
moduleSettings = variables.coldbox.getSetting( "modules" );
- if( structKeyExists( moduleSettings, thisLocationKey ) ){
+ if ( structKeyExists( moduleSettings, thisLocationKey ) ) {
return moduleSettings[ thisLocationKey ].settings;
- }
- else {
+ } else {
throw(
- type = "ColdBoxDSL.InvalidDSL",
+ 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)#"
+ detail = "The module requested: #thisLocationKey# does not exist in the loaded modules. Loaded modules are #structKeyList( moduleSettings )#"
);
}
}
- case "moduleconfig" : {
+ case "moduleconfig": {
moduleSettings = variables.coldbox.getSetting( "modules" );
- if( structKeyExists( moduleSettings, thisLocationKey ) ){
+ if ( structKeyExists( moduleSettings, thisLocationKey ) ) {
return moduleSettings[ thisLocationKey ];
- }
- else {
+ } else {
throw(
- type = "ColdBoxDSL.InvalidDSL",
+ 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)#"
+ detail = "The module requested: #thisLocationKey# does not exist in the loaded modules. Loaded modules are #structKeyList( moduleSettings )#"
);
}
}
- case "fwSetting" : case "coldboxSetting" : { return variables.coldbox.getColdBoxSetting( thisLocationKey ); }
- case "interceptor" : { return variables.coldbox.getInterceptorService().getInterceptor( thisLocationKey, true ); }
- }//end of services
+ case "fwSetting":
+ case "coldboxSetting": {
+ return variables.coldbox.getColdBoxSetting( thisLocationKey );
+ }
+ case "interceptor": {
+ return variables.coldbox.getInterceptorService().getInterceptor( thisLocationKey, true );
+ }
+ }
+ // end of services
break;
}
- //coldbox:{key}:{target}:{token}
+ // coldbox:{key}:{target}:{token}
case 4: {
- thisLocationType = getToken(thisType,2,":");
- thisLocationKey = getToken(thisType,3,":");
- thisLocationToken = getToken(thisType,4,":");
- switch(thisLocationType){
- case "modulesettings" : {
-
+ 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];
+ if ( structKeyExists( moduleSettings, thisLocationKey ) ) {
+ if ( structKeyExists( moduleSettings[ thisLocationKey ].settings, thisLocationToken ) ) {
+ return moduleSettings[ thisLocationKey ].settings[ thisLocationToken ];
} else {
throw(
- type = "ColdBoxDSL.InvalidDSL",
+ 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)#"
+ detail = "The setting requested: #thisLocationToken# does not exist in this module. Loaded settings are #structKeyList( moduleSettings[ thisLocationKey ].settings )#"
);
}
- }
- else {
+ } else {
throw(
- type = "ColdBoxDSL.InvalidDSL",
+ 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)#"
+ detail = "The module requested: #thisLocationKey# does not exist in the loaded modules. Loaded modules are #structKeyList( moduleSettings )#"
);
}
-
}
}
break;
@@ -213,10 +262,10 @@ component implements="coldbox.system.ioc.dsl.IDSLBuilder" accessors="true"{
// If we get here we have a problem.
throw(
- type = "ColdBoxDSL.InvalidDSL",
+ type = "ColdBoxDSL.InvalidDSL",
message = "The DSL provided was not valid: #arguments.definition.toString()#",
- detail = "Unknown DSL"
+ detail = "Unknown DSL"
);
}
-}
\ No newline at end of file
+}
diff --git a/system/logging/LogBox.cfc b/system/logging/LogBox.cfc
index af39acfc0..06e71e23e 100644
--- a/system/logging/LogBox.cfc
+++ b/system/logging/LogBox.cfc
@@ -8,48 +8,47 @@
*
* By default, LogBox will log any warnings pertaining to itself in the CF logs
* according to its name.
-*/
-component accessors="true"{
-
+ */
+component accessors="true" {
/**
- * The LogBox unique ID
- */
+ * The LogBox unique ID
+ */
property name="logBoxID";
/**
- * The LogBox operating version
- */
+ * The LogBox operating version
+ */
property name="version";
/**
- * The appender registration map
- */
+ * The appender registration map
+ */
property name="appenderRegistry" type="struct";
/**
- * The Logger registration map
- */
+ * The Logger registration map
+ */
property name="loggerRegistry" type="struct";
/**
- * Category based appenders
- */
+ * Category based appenders
+ */
property name="categoryAppenders";
/**
- * Configuration object
- */
+ * Configuration object
+ */
property name="config";
/**
- * ColdBox linkage class
- */
+ * ColdBox linkage class
+ */
property name="coldbox";
/**
- * WireBox linkage class
- */
+ * WireBox linkage class
+ */
property name="wirebox";
/**
@@ -60,7 +59,7 @@ component accessors="true"{
/**
* The logBox task scheduler executor
- * @see coldbox.system.async.tasks.ScheduledExecutor
+ * @see coldbox.system.async.executors.ScheduledExecutor
*/
property name="taskScheduler";
@@ -77,16 +76,16 @@ component accessors="true"{
* @return A configured and loaded LogBox instance
*/
function init(
- config="coldbox.system.logging.config.DefaultConfig",
- coldbox="",
- wirebox=""
+ config = "coldbox.system.logging.config.DefaultConfig",
+ coldbox = "",
+ wirebox = ""
){
// LogBox Unique ID
- variables.logboxID = createObject( 'java', 'java.lang.System' ).identityHashCode( this );
+ variables.logboxID = createObject( "java", "java.lang.System" ).identityHashCode( this );
// Appenders
- variables.appenderRegistry = structnew();
+ variables.appenderRegistry = structNew();
// Loggers
- variables.loggerRegistry = structnew();
+ variables.loggerRegistry = structNew();
// Category Appenders
variables.categoryAppenders = "";
// Version
@@ -109,16 +108,19 @@ component accessors="true"{
} );
// Register the task scheduler according to operating mode
- if( isObject( variables.coldbox ) ){
- variables.wirebox = variables.coldbox.getWireBox();
- variables.asyncManager = variables.coldbox.getAsyncManager();
+ if ( isObject( variables.coldbox ) ) {
+ variables.wirebox = variables.coldbox.getWireBox();
+ variables.asyncManager = variables.coldbox.getAsyncManager();
variables.taskScheduler = variables.asyncManager.getExecutor( "coldbox-tasks" );
- } else if( isObject( arguments.wirebox ) ){
- variables.asyncManager = variables.wirebox.getAsyncManager();
+ } else if ( isObject( arguments.wirebox ) ) {
+ variables.asyncManager = variables.wirebox.getAsyncManager();
variables.taskScheduler = variables.wirebox.getTaskScheduler();
} else {
- variables.asyncManager = new coldbox.system.async.AsyncManager();
- variables.taskScheduler = variables.asyncManager.newScheduledExecutor( name : "logbox-tasks", threads : 20 );
+ variables.asyncManager = new coldbox.system.async.AsyncManager();
+ variables.taskScheduler = variables.asyncManager.newScheduledExecutor(
+ name : "logbox-tasks",
+ threads: 20
+ );
}
// Configure LogBox
@@ -134,12 +136,11 @@ component accessors="true"{
* @config.doc_generic coldbox.system.logging.config.LogBoxConfig
*/
function configure( required config ){
- lock name="#variables.logBoxID#.logbox.config" type="exclusive" timeout="30" throwOnTimeout=true{
-
+ lock name="#variables.logBoxID#.logbox.config" type="exclusive" timeout="30" throwOnTimeout=true {
// Do we need to build the config object?
- if( isSimpleValue( arguments.config ) ){
+ if ( isSimpleValue( arguments.config ) ) {
arguments.config = new coldbox.system.logging.config.LogBoxConfig(
- CFCConfigPath : arguments.config
+ CFCConfigPath: arguments.config
);
}
@@ -147,31 +148,29 @@ component accessors="true"{
variables.config = arguments.config.validate();
// Reset Registries
- variables.appenderRegistry = structnew();
- variables.loggerRegistry = structnew();
+ variables.appenderRegistry = structNew();
+ variables.loggerRegistry = structNew();
- //Get appender definitions
+ // Get appender definitions
var appenders = variables.config.getAllAppenders();
// Register All Appenders configured
- for( var key in appenders ){
- registerAppender( argumentCollection=appenders[ key ] );
+ 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 )
+ var args = {
+ category : "ROOT",
+ levelMin : rootConfig.levelMin,
+ levelMax : rootConfig.levelMax,
+ appenders : getAppendersMap( rootConfig.appenders )
};
- //Save in Registry
- variables.loggerRegistry = {
- "ROOT" = new coldbox.system.logging.Logger( argumentCollection=args )
- };
+ // Save in Registry
+ variables.loggerRegistry = { "ROOT" : new coldbox.system.logging.Logger( argumentCollection = args ) };
}
}
@@ -179,14 +178,13 @@ component accessors="true"{
* Shutdown the injector gracefully by calling the shutdown events internally.
**/
function shutdown(){
-
// Check if config has onShutdown convention
- if( structKeyExists( variables.config, "onShutdown" ) ){
+ if ( structKeyExists( variables.config, "onShutdown" ) ) {
variables.config.onShutdown( this );
}
// Shutdown Executors if not in ColdBox Mode or WireBox mode
- if( !isObject( variables.coldbox ) && !isObject( variables.wirebox ) ){
+ if ( !isObject( variables.coldbox ) && !isObject( variables.wirebox ) ) {
variables.asyncManager.shutdownAllExecutors( force = true );
}
@@ -216,7 +214,7 @@ component accessors="true"{
var root = getRootLogger();
// is category object?
- if( isObject( arguments.category ) ){
+ if ( isObject( arguments.category ) ) {
arguments.category = getMetadata( arguments.category ).name;
}
@@ -224,37 +222,41 @@ component accessors="true"{
arguments.category = trim( arguments.category );
// Is logger by category name created already?
- if( structKeyExists( variables.loggerRegistry, arguments.category ) ){
+ 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 ) ){
+ 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 )
+ 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()
+ 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 ) ){
+ 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 coldbox.system.logging.Logger( argumentCollection=args );
+ var oLogger = new coldbox.system.logging.Logger( argumentCollection = args );
// Inject Root Logger
oLogger.setRootLogger( root );
// Store it
@@ -292,30 +294,29 @@ component accessors="true"{
LogBox function registerAppender(
required name,
required class,
- struct properties={},
- layout="",
- numeric levelMin=0,
- numeric levelMax=4
+ 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 ) ){
-
+ 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 it and store it
- variables.appenderRegistry[ arguments.name ] = new "#getLoggerClass( arguments.class )#"( argumentCollection=arguments )
- .setLogBox( this )
+ variables.appenderRegistry[ arguments.name ] = new "#getLoggerClass( arguments.class )#"(
+ argumentCollection = arguments
+ ).setLogBox( this )
.setColdBox( variables.coldbox )
.setWireBox( variables.wirebox )
.onRegistration()
.setInitialized( true );
-
}
-
- } // end lock
-
+ }
+ // end lock
}
return this;
@@ -335,7 +336,7 @@ component accessors="true"{
*/
private function getLoggerClass( required class ){
// is this a local class?
- if( arrayFindNoCase( variables.systemAppenders, arguments.class ) ){
+ if ( arrayFindNoCase( variables.systemAppenders, arguments.class ) ) {
return "coldbox.system.logging.appenders.#arguments.class#";
}
@@ -352,21 +353,25 @@ component accessors="true"{
var parentCategory = "";
// category len check
- if( len( arguments.category ) ){
- parentCategory = listDeleteAt( arguments.category, listLen( arguments.category, "." ), "." );
+ if ( len( arguments.category ) ) {
+ parentCategory = listDeleteAt(
+ arguments.category,
+ listLen( arguments.category, "." ),
+ "."
+ );
}
// Check if parent Category is empty
- if( len( parentCategory ) EQ 0 ){
+ 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 ) ){
+ if ( structKeyExists( variables.loggerRegistry, parentCategory ) ) {
return variables.loggerRegistry[ parentCategory ];
}
// Do we need to create it, lazy loading?
- if( variables.config.categoryExists( parentCategory ) ){
+ if ( variables.config.categoryExists( parentCategory ) ) {
return getLogger( parentCategory );
}
// Else, it was not located, recurse
@@ -383,7 +388,7 @@ component accessors="true"{
.listToArray()
.reduce( function( result, item, index ){
var target = {};
- if( !isNull( arguments.result ) ){
+ if ( !isNull( arguments.result ) ) {
target = result;
}
target[ item ] = variables.appenderRegistry[ item ];
@@ -400,4 +405,4 @@ component accessors="true"{
return new coldbox.system.core.util.Util();
}
-}
\ No newline at end of file
+}
diff --git a/system/web/Controller.cfc b/system/web/Controller.cfc
index 18f749b64..180de253d 100644
--- a/system/web/Controller.cfc
+++ b/system/web/Controller.cfc
@@ -76,7 +76,7 @@ component serializable="false" accessors="true" {
function init( required appRootPath, appKey = "cbController" ){
// These will be lazy loaded on first use since the framework isn't ready to create it yet
variables.renderer = "";
- variables.wireBox = "";
+ variables.wireBox = "";
// Create Utility
variables.util = new coldbox.system.core.util.Util();
@@ -112,7 +112,6 @@ component serializable="false" accessors="true" {
// LogBox Default Configuration & Creation
variables.logBox = services.loaderService.createDefaultLogBox();
variables.log = variables.logBox.getLogger( this );
-
variables.log.info( "+ LogBox created" );
// Setup the ColdBox Services
@@ -121,6 +120,7 @@ component serializable="false" accessors="true" {
services.handlerService = new coldbox.system.web.services.HandlerService( this );
services.routingService = new coldbox.system.web.services.RoutingService( this );
services.moduleService = new coldbox.system.web.services.ModuleService( this );
+ services.schedulerService = new coldbox.system.web.services.SchedulerService( this );
variables.log.info( "+ ColdBox services created" );
@@ -221,6 +221,13 @@ component serializable="false" accessors="true" {
return services.routingService;
}
+ /**
+ * Get the scheduling service
+ */
+ function getSchedulerService(){
+ return services.schedulerService;
+ }
+
/****************************************************************
* Setting Methods *
****************************************************************/
@@ -362,8 +369,8 @@ component serializable="false" accessors="true" {
if ( len( trim( arguments.event ) ) eq 0 ) {
arguments.event = getSetting( "DefaultEvent" );
}
- // Query String Struct to String
- if( isStruct( arguments.queryString ) ){
+ // Query String Struct to String
+ if ( isStruct( arguments.queryString ) ) {
arguments.queryString = arguments.queryString
.reduce( function( result, key, value ){
arguments.result.append( "#encodeForURL( arguments.key )#=#encodeForURL( arguments.value )#" );
@@ -422,19 +429,9 @@ component serializable="false" accessors="true" {
// If the routestring ends with '/' we do not want to
// double append '/'
if ( right( routeString, 1 ) NEQ "/" ) {
- routeString = routeString & "/" & replace(
- arguments.queryString,
- "&",
- "/",
- "all"
- );
+ routeString = routeString & "/" & replace( arguments.queryString, "&", "/", "all" );
} else {
- routeString = routeString & replace(
- arguments.queryString,
- "&",
- "/",
- "all"
- );
+ routeString = routeString & replace( arguments.queryString, "&", "/", "all" );
}
routeString = replace( routeString, "=", "/", "all" );
}
@@ -590,10 +587,7 @@ component serializable="false" accessors="true" {
);
}
- throw(
- type = "InvalidArgumentException",
- message = "The named route '#arguments.name#' does not exist"
- );
+ throw( type = "InvalidArgumentException", message = "The named route '#arguments.name#' does not exist" );
}
/**
@@ -672,10 +666,7 @@ component serializable="false" accessors="true" {
// Do action Rendering
services.requestService
.getContext()
- .renderdata(
- type = results.ehBean.getActionMetadata( "renderdata" ),
- data = results.data
- );
+ .renderdata( type = results.ehBean.getActionMetadata( "renderdata" ), data = results.data );
}
// Are we caching
@@ -883,11 +874,9 @@ component serializable="false" accessors="true" {
// Verify if event was overridden
if ( arguments.defaultEvent and arguments.event NEQ oRequestContext.getCurrentEvent() ) {
// Validate the overridden event
- results.ehBean = services.handlerService.getHandlerBean(
- oRequestContext.getCurrentEvent()
- );
+ results.ehBean = services.handlerService.getHandlerBean( oRequestContext.getCurrentEvent() );
// Get new handler to follow execution
- oHandler = services.handlerService.getHandler( results.ehBean, oRequestContext );
+ oHandler = services.handlerService.getHandler( results.ehBean, oRequestContext );
}
// Invoke onMissingAction event
@@ -1108,12 +1097,7 @@ component serializable="false" accessors="true" {
){
if (
(
- (
- len( arguments.inclusion ) AND listFindNoCase(
- arguments.inclusion,
- arguments.action
- )
- )
+ ( len( arguments.inclusion ) AND listFindNoCase( arguments.inclusion, arguments.action ) )
OR
( NOT len( arguments.inclusion ) )
)
diff --git a/system/web/services/ModuleService.cfc b/system/web/services/ModuleService.cfc
index 403ca3d9d..e85f13f51 100755
--- a/system/web/services/ModuleService.cfc
+++ b/system/web/services/ModuleService.cfc
@@ -136,9 +136,7 @@ component extends="coldbox.system.web.services.BaseService" {
}
}
- variables.logger.info(
- "+ Registered All Modules in #numberFormat( getTickCount() - totalTime )# ms"
- );
+ variables.logger.info( "+ Registered All Modules in #numberFormat( getTickCount() - totalTime )# ms" );
// interception
variables.interceptorService.announce(
@@ -178,7 +176,7 @@ component extends="coldbox.system.web.services.BaseService" {
boolean force = false
){
// Module To Load
- var sTime = getTickCount();
+ var sTime = getTickCount();
var modName = arguments.moduleName;
var modulesConfiguration = controller.getSetting( "modules" );
var appSettings = controller.getConfigSettings();
@@ -187,9 +185,7 @@ component extends="coldbox.system.web.services.BaseService" {
// Check if incoming invocation path is sent, if so, register as new module
if ( len( arguments.invocationPath ) ) {
// Check if passed module name is already registered
- if (
- structKeyExists( variables.moduleRegistry, arguments.moduleName ) AND !arguments.force
- ) {
+ if ( structKeyExists( variables.moduleRegistry, arguments.moduleName ) AND !arguments.force ) {
variables.logger.info(
"> The module #arguments.moduleName# has already been registered, so skipping registration"
);
@@ -197,10 +193,8 @@ component extends="coldbox.system.web.services.BaseService" {
}
// register new incoming location
variables.moduleRegistry[ arguments.moduleName ] = {
- locationPath : "/" & replace( arguments.invocationPath, ".", "/", "all" ),
- physicalPath : expandPath(
- "/" & replace( arguments.invocationPath, ".", "/", "all" )
- ),
+ locationPath : "/" & replace( arguments.invocationPath, ".", "/", "all" ),
+ physicalPath : expandPath( "/" & replace( arguments.invocationPath, ".", "/", "all" ) ),
invocationPath : arguments.invocationPath
};
}
@@ -329,21 +323,31 @@ component extends="coldbox.system.web.services.BaseService" {
interceptors : [],
interceptorSettings : { customInterceptionPoints : "" },
layoutSettings : { defaultLayout : "" },
+ // Routing + resources
routes : [],
resources : [],
+ // Module Conventions
conventions : {
- handlersLocation : "handlers",
- layoutsLocation : "layouts",
- viewsLocation : "views",
- modelsLocation : "models",
- includesLocation : "includes",
- routerLocation : "config.Router"
+ handlersLocation : "handlers",
+ layoutsLocation : "layouts",
+ viewsLocation : "views",
+ modelsLocation : "models",
+ includesLocation : "includes",
+ routerLocation : "config.Router",
+ schedulerLocation : "config.Scheduler"
},
- childModules : [],
- parent : arguments.parent,
- router : "",
- routerInvocationPath : modulesInvocationPath & "." & modName,
- routerPhysicalPath : modLocation
+ // My Children
+ childModules : [],
+ // My Daddy!
+ parent : arguments.parent,
+ // Module Router
+ router : "",
+ routerInvocationPath : modulesInvocationPath & "." & modName,
+ routerPhysicalPath : modLocation,
+ // Task Scheduler
+ scheduler : "",
+ schedulerInvocationPath : modulesInvocationPath & "." & modName,
+ schedulerPhysicalpath : modLocation
};
// Load Module configuration from cfc and store it in module Config Cache
@@ -352,9 +356,7 @@ component extends="coldbox.system.web.services.BaseService" {
// Verify if module has been disabled
if ( mConfig.disabled ) {
if ( variables.logger.canInfo() ) {
- variables.logger.info(
- "> Skipping module: #arguments.moduleName# as it has been disabled!"
- );
+ variables.logger.info( "> Skipping module: #arguments.moduleName# as it has been disabled!" );
}
return false;
} else {
@@ -385,8 +387,12 @@ component extends="coldbox.system.web.services.BaseService" {
"all"
)#";
mConfig.modelsPhysicalPath &= "/#mConfig.conventions.modelsLocation#";
+ // Router
mConfig.routerInvocationPath &= ".#mConfig.conventions.routerLocation#";
mConfig.routerPhysicalPath &= "/#mConfig.conventions.routerLocation.replace( ".", "/", "all" )#.cfc";
+ // Scheduler
+ mConfig.schedulerInvocationPath &= ".#mConfig.conventions.schedulerLocation#";
+ mConfig.schedulerPhysicalPath &= "/#mConfig.conventions.schedulerLocation.replace( ".", "/", "all" )#.cfc";
// Register CFML Mapping if it exists, for loading purposes
if ( len( trim( mConfig.cfMapping ) ) ) {
@@ -432,7 +438,8 @@ component extends="coldbox.system.web.services.BaseService" {
}
}
}
- } // end inception loading
+ }
+ // end inception loading
// Log Registration Time
mConfig.registrationTime = getTickCount() - sTime;
@@ -479,9 +486,7 @@ component extends="coldbox.system.web.services.BaseService" {
}
}
- variables.logger.info(
- "+ Activated All Modules in #numberFormat( getTickCount() - totalTime )# ms"
- );
+ variables.logger.info( "+ Activated All Modules in #numberFormat( getTickCount() - totalTime )# ms" );
// interception
variables.interceptorService.announce(
@@ -500,24 +505,22 @@ component extends="coldbox.system.web.services.BaseService" {
* @return The Service
*/
ModuleService function activateModule( required moduleName ){
- var sTime = getTickCount();
+ var sTime = getTickCount();
var modules = variables.registeredModules;
// If module not registered, throw exception
if ( isNull( modules[ arguments.moduleName ] ) ) {
throw(
- message : "Cannot activate module: #arguments.moduleName#. Already processed #structKeyList( modules )#",
- detail : "The module has not been registered, register the module first and then activate it.",
- type : "IllegalModuleState"
+ message: "Cannot activate module: #arguments.moduleName#. Already processed #structKeyList( modules )#",
+ detail : "The module has not been registered, register the module first and then activate it.",
+ type : "IllegalModuleState"
);
}
// Check if module already activated
if ( modules[ arguments.moduleName ].activated ) {
// Log it
- variables.logger.debug(
- "==> Module '#arguments.moduleName#' already activated, skipping activation."
- );
+ variables.logger.debug( "==> Module '#arguments.moduleName#' already activated, skipping activation." );
return this;
}
@@ -535,9 +538,7 @@ component extends="coldbox.system.web.services.BaseService" {
// Do we have dependencies to activate first
mConfig.dependencies.each( function( thisDependency ){
- variables.logger.debug(
- "==> Activating '#moduleName#' dependency: #thisDependency#"
- );
+ variables.logger.debug( "==> Activating '#moduleName#' dependency: #thisDependency#" );
// Activate dependency first
activateModule( thisDependency );
} );
@@ -594,10 +595,7 @@ component extends="coldbox.system.web.services.BaseService" {
);
} else {
// just register with no namespace
- binder.mapDirectory(
- packagePath = packagePath,
- process = mConfig.autoProcessModels
- );
+ binder.mapDirectory( packagePath = packagePath, process = mConfig.autoProcessModels );
}
// Register Default Module Export if it exists as @moduleName, so you can do getInstance( "@moduleName" )
@@ -642,11 +640,7 @@ component extends="coldbox.system.web.services.BaseService" {
}
// Store Inherited Entry Point
- mConfig.inheritedEntryPoint = parentEntryPoint & reReplace(
- mConfig.entryPoint,
- "^/",
- ""
- );
+ mConfig.inheritedEntryPoint = parentEntryPoint & reReplace( mConfig.entryPoint, "^/", "" );
// Register Module Routing Entry Point + Struct Literals for routes and resources
appRouter.addModuleRoutes(
@@ -681,9 +675,7 @@ component extends="coldbox.system.web.services.BaseService" {
var conventionsRouteExists = mConfig.router
.getRoutes()
.find( function( item ){
- return (
- item.pattern == "/:handler/:action?" || item.pattern == ":handler/:action?"
- );
+ return ( item.pattern == "/:handler/:action?" || item.pattern == ":handler/:action?" );
} );
if ( conventionsRouteExists == 0 ) {
mConfig.router.route( "/:handler/:action?" ).end();
@@ -716,14 +708,20 @@ component extends="coldbox.system.web.services.BaseService" {
// Register Executors if any are registered
mConfig.executors.each( function( key, config ){
arguments.config.name = arguments.key;
- variables.controller
- .getAsyncManager()
- .newExecutor( argumentCollection = arguments.config );
- variables.logger.info(
- "+ Registered Module (#moduleName#) Executor: #arguments.key#"
- );
+ variables.controller.getAsyncManager().newExecutor( argumentCollection = arguments.config );
+ variables.logger.info( "+ Registered Module (#moduleName#) Executor: #arguments.key#" );
} );
+ // Register Scheduler if it exists as scheduler@moduleName
+ if ( fileExists( mConfig.schedulerPhysicalPath ) ) {
+ mConfig.scheduler = variables.controller
+ .getSchedulerService()
+ .loadScheduler(
+ name: "cbScheduler@#arguments.moduleName#",
+ path: mConfig.schedulerInvocationPath
+ );
+ }
+
// Call on module configuration object onLoad() if found
if ( structKeyExists( variables.mConfigCache[ arguments.moduleName ], "onLoad" ) ) {
variables.mConfigCache[ arguments.moduleName ].onLoad();
@@ -751,7 +749,9 @@ component extends="coldbox.system.web.services.BaseService" {
);
// Log it
- variables.logger.info( "+ Module (#arguments.moduleName#) Activated (#mConfig.activationTime#ms) => { version: #mConfig.version#, from: #mConfig.path# }" );
+ variables.logger.info(
+ "+ Module (#arguments.moduleName#) Activated (#mConfig.activationTime#ms) => { version: #mConfig.version#, from: #mConfig.path# }"
+ );
}
// end lock
@@ -836,10 +836,7 @@ component extends="coldbox.system.web.services.BaseService" {
var mConfig = appConfig.modules[ arguments.moduleName ];
// Before unloading a module interception
- variables.interceptorService.announce(
- "preModuleUnload",
- { moduleName : arguments.moduleName }
- );
+ variables.interceptorService.announce( "preModuleUnload", { moduleName : arguments.moduleName } );
// Call on module configuration object onLoad() if found
if ( structKeyExists( variables.mConfigCache[ arguments.moduleName ], "onUnload" ) ) {
@@ -854,17 +851,17 @@ component extends="coldbox.system.web.services.BaseService" {
}
}
+ // Unregister scheduler if loaded
+ if ( isObject( mConfig.scheduler ) ) {
+ variables.controller.getSchedulerService().removeScheduler( mConfig.scheduler.getName() );
+ }
+
// Unregister app Helpers
if ( arrayLen( mConfig.applicationHelper ) ) {
controller.setSetting(
"applicationHelper",
arrayFilter( controller.getSetting( "applicationHelper" ), function( helper ){
- return (
- !arrayFindNoCase(
- appConfig.modules[ moduleName ].applicationHelper,
- helper
- )
- );
+ return ( !arrayFindNoCase( appConfig.modules[ moduleName ].applicationHelper, helper ) );
} )
);
}
@@ -879,9 +876,7 @@ component extends="coldbox.system.web.services.BaseService" {
// Remove SES if enabled.
if ( controller.settingExists( "sesBaseURL" ) ) {
- variables.wirebox
- .getInstance( "router@coldbox" )
- .removeModuleRoutes( arguments.moduleName );
+ variables.wirebox.getInstance( "router@coldbox" ).removeModuleRoutes( arguments.moduleName );
}
// Remove executors
@@ -896,10 +891,7 @@ component extends="coldbox.system.web.services.BaseService" {
structDelete( variables.mConfigCache, arguments.moduleName );
// After unloading a module interception
- variables.interceptorService.announce(
- "postModuleUnload",
- { moduleName : arguments.moduleName }
- );
+ variables.interceptorService.announce( "postModuleUnload", { moduleName : arguments.moduleName } );
// Log it
if ( variables.logger.canInfo() ) {
@@ -941,9 +933,7 @@ component extends="coldbox.system.web.services.BaseService" {
var appSettings = controller.getConfigSettings();
// Build a new router for this module so we can track its routes
- arguments.config.router = variables.wirebox.getInstance(
- "coldbox.system.web.routing.Router"
- );
+ arguments.config.router = variables.wirebox.getInstance( "coldbox.system.web.routing.Router" );
// MixIn Variables Scope
oConfig
@@ -1023,9 +1013,7 @@ component extends="coldbox.system.web.services.BaseService" {
// Dependencies
if ( structKeyExists( oConfig, "dependencies" ) ) {
// set it always as an array
- mConfig.dependencies = isSimpleValue( oConfig.dependencies ) ? listToArray(
- oConfig.dependencies
- ) : oConfig.dependencies;
+ mConfig.dependencies = isSimpleValue( oConfig.dependencies ) ? listToArray( oConfig.dependencies ) : oConfig.dependencies;
}
// Application Helpers
if ( structKeyExists( oConfig, "applicationHelper" ) ) {
diff --git a/system/web/services/SchedulerService.cfc b/system/web/services/SchedulerService.cfc
new file mode 100644
index 000000000..685a8858a
--- /dev/null
+++ b/system/web/services/SchedulerService.cfc
@@ -0,0 +1,153 @@
+/**
+ * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp
+ * www.ortussolutions.com
+ * ---
+ * This service manages all schedulers in ColdBox in an HMVC fashion
+ */
+component extends="coldbox.system.web.services.BaseService" accessors="true" {
+
+ /**
+ * --------------------------------------------------------------------------
+ * Properties
+ * --------------------------------------------------------------------------
+ */
+
+ /**
+ * A collection of schedulers this manager manages
+ */
+ property name="schedulers" type="struct";
+
+ /**
+ * Constructor
+ */
+ function init( required controller ){
+ variables.controller = arguments.controller;
+ // Register a fresh collection of schedulers
+ variables.schedulers = structNew( "ordered" );
+
+ return this;
+ }
+
+ /**
+ * Once configuration loads prepare for operation
+ */
+ function onConfigurationLoad(){
+ // Prepare references for faster access
+ variables.log = variables.controller.getLogBox().getLogger( this );
+ variables.interceptorService = variables.controller.getInterceptorService();
+ variables.wirebox = variables.controller.getWireBox();
+ variables.appMapping = variables.controller.getSetting( "AppMapping" );
+ variables.appPath = variables.controller.getSetting( "applicationPath" );
+ variables.baseScheduler = "coldbox.system.web.tasks.ColdBoxScheduler";
+ // Load up the global app scheduler
+ loadGlobalScheduler();
+ }
+
+ /**
+ * Process a ColdBox service shutdown
+ */
+ function onShutdown(){
+ variables.schedulers.each( function( name, thisScheduler ){
+ variables.log.info( "† Shutting down Scheduler (#arguments.name#)..." );
+ arguments.thisScheduler.shutdown();
+ } );
+ }
+
+ /**
+ * Load the application's global scheduler
+ */
+ function loadGlobalScheduler(){
+ var appSchedulerConvention = "config.Scheduler";
+ var schedulerName = "appScheduler@coldbox";
+ var schedulerPath = variables.baseScheduler;
+
+ // Check if base scheduler has been mapped?
+ if ( NOT variables.wirebox.getBinder().mappingExists( variables.baseScheduler ) ) {
+ // feed the base class
+ variables.wirebox
+ .registerNewInstance( name = variables.baseScheduler, instancePath = variables.baseScheduler )
+ .addDIConstructorArgument( name = "name", value = "variables.baseScheduler" );
+ }
+
+ // Check if the convention exists, else just build out a simple scheduler
+ if ( fileExists( variables.appPath & "config/Scheduler.cfc" ) ) {
+ schedulerPath = (
+ variables.appMapping.len() ? "#variables.appMapping#.#appSchedulerConvention#" : appSchedulerConvention
+ );
+ }
+
+ // Load, create, register and activate
+ loadScheduler( schedulerName, schedulerPath );
+ }
+
+ /**
+ * Load a scheduler cfc by path and name, usually this is called from module services or ways to register
+ * a-la-carte schedulers
+ *
+ * @name The name to register the scheduler with
+ * @path The path to instantiate the scheduler cfc
+ *
+ * @return The created, configured, registered, and activated scheduler
+ */
+ function loadScheduler( required name, required path ){
+ // Log it
+ variables.log.info( "Loading ColdBox Task Scheduler (#arguments.name#) at => #arguments.path#..." );
+ // Process as a Scheduler.cfc with virtual inheritance
+ wirebox
+ .registerNewInstance( name = arguments.name, instancePath = arguments.path )
+ .setVirtualInheritance( variables.baseScheduler )
+ .setThreadSafe( true )
+ .setScope( variables.wirebox.getBinder().SCOPES.SINGLETON )
+ .addDIConstructorArgument( name = "name", value = arguments.name );
+ // Create, register, configure it and start it up baby!
+ var oScheduler = registerScheduler( variables.wirebox.getInstance( arguments.name ) );
+ // Register the Scheduler as an Interceptor as well.
+ variables.controller.getInterceptorService().registerInterceptor( interceptorObject = oScheduler );
+ // Configure it
+ oScheduler.configure();
+ // Start it up
+ oScheduler.startup();
+ // Return it
+ return oScheduler;
+ }
+
+ /**
+ * Register a new scheduler in this manager using the scheduler name
+ *
+ * @scheduler The scheduler object to register in the service
+ *
+ * @return The registered scheduler Object: coldbox.system.web.tasks.ColdBoxScheduler
+ */
+ function registerScheduler( required scheduler ){
+ // Register it
+ variables.schedulers[ arguments.scheduler.getName() ] = arguments.scheduler;
+ // Return it
+ return arguments.scheduler;
+ }
+
+ /**
+ * Verify if a scheduler has been registered
+ *
+ * @name The name of the scheduler
+ */
+ boolean function hasScheduler( required name ){
+ return variables.schedulers.keyExists( arguments.name );
+ }
+
+ /**
+ * Remove a scheduler from this manager
+ *
+ * @name The name of the scheduler
+ *
+ * @return True if removed, else if not found or not removed
+ */
+ boolean function removeScheduler( required name ){
+ if ( hasScheduler( arguments.name ) ) {
+ variables.schedulers[ arguments.name ].shutdown();
+ structDelete( variables.schedulers, arguments.name );
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/system/web/tasks/ColdBoxScheduledTask.cfc b/system/web/tasks/ColdBoxScheduledTask.cfc
new file mode 100644
index 000000000..809ca3f74
--- /dev/null
+++ b/system/web/tasks/ColdBoxScheduledTask.cfc
@@ -0,0 +1,206 @@
+/**
+ * This object represents a scheduled task that will be sent in to a scheduled executor for scheduling.
+ * It has a fluent and human dsl for setting it up and restricting is scheduling and frequency of scheduling.
+ *
+ * A task can be represented as either a closure or a cfc with a `run()` or custom runnable method.
+ */
+component extends="coldbox.system.async.tasks.ScheduledTask" accessors="true" {
+
+ /**
+ * --------------------------------------------------------------------------
+ * DI
+ * --------------------------------------------------------------------------
+ */
+
+ property name="controller" inject="coldbox";
+ property name="wirebox" inject="wirebox";
+ property name="cachebox" inject="cachebox";
+ property name="log" inject="logbox:logger:{this}";
+
+ /**
+ * --------------------------------------------------------------------------
+ * Properties
+ * --------------------------------------------------------------------------
+ */
+
+ /**
+ * Execution Environment
+ */
+ property name="environments" type="array";
+
+ /**
+ * This indicates that the task should ONLY run on one server and not on all servers clustered for the application.
+ * Please note that this will ONLY work if you are using a distributed cache in your application via CacheBox.
+ * The default cache region we will use is the template
cache, which you can connect to any distributed
+ * caching engine like: Redis, Couchbase, Mongo, Elastic, DB etc.
+ */
+ property name="serverFixation" type="boolean";
+
+ /**
+ * The cache name to use for server fixation and more. By default we use the template
region
+ */
+ property name="cacheName";
+
+ /**
+ * How long does the server fixation lock remain for. Deafult is 60 minutes.
+ */
+ property name="serverLockTimeout" type="numeric";
+
+ /**
+ * Constructor
+ *
+ * @name The name of this task
+ * @executor The executor this task will run under and be linked to
+ * @task The closure or cfc that represents the task (optional)
+ * @method The method on the cfc to call, defaults to "run" (optional)
+ */
+ ColdBoxScheduledTask function init(
+ required name,
+ required executor,
+ any task = "",
+ method = "run"
+ ){
+ // init
+ super.init( argumentCollection = arguments );
+ // seed environments
+ variables.environments = [];
+ // Can we run on all servers, or just one
+ variables.serverFixation = false;
+ // How long in minutes will the lock be set for before it expires.
+ variables.serverLockTimeout = 60;
+ // CacheBox Region
+ variables.cacheName = "template";
+
+ return this;
+ }
+
+ /**
+ * Set the environments that this task can run under ONLY
+ *
+ * @environment A string, a list, or an array of environments
+ */
+ ColdBoxScheduledTask function onEnvironment( required environment ){
+ if ( isSimpleValue( arguments.environment ) ) {
+ arguments.environment = listToArray( arguments.environment );
+ }
+ variables.environments = arguments.environment;
+ return this;
+ }
+
+ /**
+ * This indicates that the task should ONLY run on one server and not on all servers clustered for the application.
+ * Please note that this will ONLY work if you are using a distributed cache in your application via CacheBox.
+ * The default cache region we will use is the template
cache, which you can connect to any distributed
+ * caching engine like: Redis, Couchbase, Mongo, Elastic, DB etc.
+ */
+ ColdBoxScheduledTask function onOneServer(){
+ variables.serverFixation = true;
+ return this;
+ }
+
+ /**
+ * This method verifies if the running task is constrained to run on specific valid constraints:
+ *
+ * - when
+ * - dayOfTheMonth
+ * - dayOfTheWeek
+ * - lastBusinessDay
+ * - weekends
+ * - weekdays
+ * - environments
+ * - server fixation
+ *
+ * This method is called by the `run()` method at runtime to determine if the task can be ran at that point in time
+ */
+ boolean function isConstrained(){
+ // Call super and if constrained already, then just exit out.
+ if ( super.isConstrained() ) {
+ return true;
+ }
+
+ // Environments Check
+ if (
+ variables.environments.len() && !arrayContainsNoCase(
+ variables.environments,
+ variables.controller.getSetting( "environment" )
+ )
+ ) {
+ variables.log.info(
+ "Skipping task (#getName()#) as it is constrained in the current environment: #variables.controller.getSetting( "environment" )#"
+ );
+ return true;
+ }
+
+ // Server fixation constrained
+ if ( variables.serverFixation && !canRunOnThisServer() ) {
+ return true;
+ }
+
+ // Not constrained, run it!
+ return false;
+ }
+
+ /**
+ * This method is called ALWAYS after a task runs, wether in failure or success but used internally for
+ * any type of cleanups
+ */
+ function cleanupTaskRun(){
+ // Cleanup server fixation locks after task execution, wether failure or success
+ // This way, the tasks can run on a round-robin approach on a clustered environment
+ // and not fixated on a specific server
+ getCache().clear( "cbtasks-server-fixation-#replace( getName(), " ", "-", "all" )#" );
+ }
+
+ /**
+ * Verifies if a task can run on the executed server by using our distributed cache lock strategy
+ */
+ boolean function canRunOnThisServer(){
+ var keyName = "cbtasks-server-fixation-#replace( getName(), " ", "-", "all" )#";
+ // Get or set the lock, first one wins!
+ getCache().getOrSet(
+ // key
+ keyName,
+ // producer
+ function(){
+ return {
+ "task" : getName(),
+ "lockOn" : now(),
+ "serverHost" : getStats().inetHost,
+ "serverIp" : getStats().localIp
+ };
+ },
+ // timeout in minutes: defaults to 60 minutes and 0 last access timeout
+ variables.serverLockTimeout,
+ 0
+ );
+ // Get the lock now. At least one server must have set it by now
+ var serverLock = getCache().get( keyName );
+ // If no lock something really went wrong, so constrain it and log it
+ if ( isNull( serverLock ) || !isStruct( serverLock ) ) {
+ variables.log.error(
+ "Server lock for task (#getName()#) is null or not a struct, something is wrong with the cache set, please verify it with key (#keyName#).",
+ ( !isNull( serverLock ) ? serverLock : "" )
+ );
+ return false;
+ }
+ // Else, it exists, check we are the same server that locked! If true, then we can run it baby!
+ else if ( serverLock.serverHost eq getStats().inetHost && serverLock.serverIp eq getStats().localIp ) {
+ return true;
+ } else {
+ variables.log.info(
+ "Skipping task (#getName()#) as it is constrained to run on one server (#serverLock.serverHost#/#serverLock.serverIp#). This server (#getStats().inetHost#/#getStats().localIp#) is different."
+ );
+ return false;
+ }
+ }
+
+ /**
+ * This method retrieves the selected CacheBox provider that will be used for server fixation and much more.
+ *
+ * @return coldbox.system.cache.providers.IColdBoxProvider
+ */
+ function getCache(){
+ return variables.cachebox.getCache( variables.cacheName );
+ }
+
+}
diff --git a/system/web/tasks/ColdBoxScheduler.cfc b/system/web/tasks/ColdBoxScheduler.cfc
new file mode 100644
index 000000000..6f41c7a72
--- /dev/null
+++ b/system/web/tasks/ColdBoxScheduler.cfc
@@ -0,0 +1,500 @@
+/**
+ * The Async Scheduler is in charge of registering scheduled tasks, starting them, monitoring them and shutting them down if needed.
+ *
+ * Each scheduler is bound to an scheduled executor class. You can override the executor using the `setExecutor()` method if you so desire.
+ * The scheduled executor will be named {name}-scheduler
+ *
+ * In a ColdBox context, you might have the global scheduler in charge of the global tasks and also 1 per module as well in HMVC fashion.
+ * In a ColdBox context, this object will inherit from the ColdBox super type as well dynamically at runtime.
+ *
+ */
+component
+ extends ="coldbox.system.async.tasks.Scheduler"
+ accessors="true"
+ singleton
+{
+
+ /**
+ * --------------------------------------------------------------------------
+ * Properties
+ * --------------------------------------------------------------------------
+ */
+
+ /**
+ * ColdBox controller
+ */
+ property name="controller";
+
+ /**
+ * CacheBox
+ */
+ property name="cachebox";
+
+ /**
+ * WireBox
+ */
+ property name="wirebox";
+
+ /**
+ * Logger
+ */
+ property name="log";
+
+ /**
+ * The cache name to use for server fixation and more. By default we use the template
region
+ */
+ property name="cacheName";
+
+ /**
+ * The bit that can be used to set all tasks created by this scheduler to always run on one server
+ */
+ property name="serverFixation" type="boolean";
+
+ /**
+ * Constructor
+ *
+ * @name The name of this scheduler
+ * @asyncManager The async manager we are linked to
+ * @asyncManager.inject coldbox:asyncManager
+ * @controller The coldBox controller
+ * @controller.inject coldbox
+ */
+ function init(
+ required name,
+ required asyncManager,
+ required controller
+ ){
+ // Super init
+ super.init( arguments.name, arguments.asyncManager );
+ // Controller
+ variables.controller = arguments.controller;
+ // Register Log object
+ variables.log = variables.controller.getLogBox().getLogger( this );
+ // Register CacheBox
+ variables.cacheBox = arguments.controller.getCacheBox();
+ // Register WireBox
+ variables.wireBox = arguments.controller.getWireBox();
+ // CacheBox Region
+ variables.cacheName = "template";
+ // Server fixation
+ variables.serverFixation = false;
+ return this;
+ }
+
+ /**
+ * --------------------------------------------------------------------------
+ * Overidden Methods
+ * --------------------------------------------------------------------------
+ */
+
+ /**
+ * Register a new task in this scheduler that will be executed once the `startup()` is fired or manually
+ * via the run() method of the task.
+ *
+ * @return a ScheduledTask object so you can work on the registration of the task
+ */
+ ColdBoxScheduledTask function task( required name ){
+ // Create task with custom name
+ var oColdBoxTask = variables.wirebox
+ .getInstance(
+ "coldbox.system.web.tasks.ColdBoxScheduledTask",
+ { name : arguments.name, executor : variables.executor }
+ )
+ // Set ourselves into the task
+ .setScheduler( this )
+ // Set the default cachename into the task
+ .setCacheName( getCacheName() )
+ // Server fixation
+ .setServerFixation( getServerFixation() )
+ // Set default timezone into the task
+ .setTimezone( getTimezone().getId() );
+
+ // Register the task by name
+ variables.tasks[ arguments.name ] = {
+ // task name
+ "name" : arguments.name,
+ // task object
+ "task" : oColdBoxTask,
+ // task scheduled future object
+ "future" : "",
+ // when it registers
+ "registeredAt" : now(),
+ // when it's scheduled
+ "scheduledAt" : "",
+ // Tracks if the task has been disabled for startup purposes
+ "disabled" : false,
+ // If there is an error scheduling the task
+ "error" : false,
+ // Any error messages when scheduling
+ "errorMessage" : "",
+ // The exception stacktrace if something went wrong scheduling the task
+ "stacktrace" : "",
+ // Server Host
+ "inetHost" : variables.util.discoverInetHost(),
+ // Server IP
+ "localIp" : variables.util.getServerIp()
+ };
+
+ return oColdBoxTask;
+ }
+
+
+ /**
+ * --------------------------------------------------------------------------
+ * ColdBox Methods
+ * --------------------------------------------------------------------------
+ */
+
+ /**
+ * Get a instance object from WireBox
+ *
+ * @name The mapping name or CFC path or DSL to retrieve
+ * @initArguments The constructor structure of arguments to passthrough when initializing the instance
+ * @dsl The DSL string to use to retrieve an instance
+ *
+ * @return The requested instance
+ */
+ function getInstance( name, initArguments = {}, dsl ){
+ return variables.controller.getWirebox().getInstance( argumentCollection = arguments );
+ }
+
+ /**
+ * Retrieve the system web renderer
+ *
+ * @return coldbox.system.web.Renderer
+ */
+ function getRenderer(){
+ return variables.controller.getRenderer();
+ }
+
+ /**
+ * Render out a view
+ *
+ * @view The the view to render, if not passed, then we look in the request context for the current set view.
+ * @args A struct of arguments to pass into the view for rendering, will be available as 'args' in the view.
+ * @module The module to render the view from explicitly
+ * @cache Cached the view output or not, defaults to false
+ * @cacheTimeout The time in minutes to cache the view
+ * @cacheLastAccessTimeout The time in minutes the view will be removed from cache if idle or requested
+ * @cacheSuffix The suffix to add into the cache entry for this view rendering
+ * @cacheProvider The provider to cache this view in, defaults to 'template'
+ * @collection A collection to use by this Renderer to render the view as many times as the items in the collection (Array or Query)
+ * @collectionAs The name of the collection variable in the partial rendering. If not passed, we will use the name of the view by convention
+ * @collectionStartRow The start row to limit the collection rendering with
+ * @collectionMaxRows The max rows to iterate over the collection rendering with
+ * @collectionDelim A string to delimit the collection renderings by
+ * @prePostExempt If true, pre/post view interceptors will not be fired. By default they do fire
+ * @name The name of the rendering region to render out, Usually all arguments are coming from the stored region but you override them using this function's arguments.
+ *
+ * @return The rendered view
+ */
+ function view(
+ view = "",
+ struct args = {},
+ module = "",
+ boolean cache = false,
+ cacheTimeout = "",
+ cacheLastAccessTimeout = "",
+ cacheSuffix = "",
+ cacheProvider = "template",
+ collection,
+ collectionAs = "",
+ numeric collectionStartRow = "1",
+ numeric collectionMaxRows = 0,
+ collectionDelim = "",
+ boolean prePostExempt = false,
+ name
+ ){
+ return variables.controller.getRenderer().renderView( argumentCollection = arguments );
+ }
+
+ /**
+ * Renders an external view anywhere that cfinclude works.
+ *
+ * @view The the view to render
+ * @args A struct of arguments to pass into the view for rendering, will be available as 'args' in the view.
+ * @cache Cached the view output or not, defaults to false
+ * @cacheTimeout The time in minutes to cache the view
+ * @cacheLastAccessTimeout The time in minutes the view will be removed from cache if idle or requested
+ * @cacheSuffix The suffix to add into the cache entry for this view rendering
+ * @cacheProvider The provider to cache this view in, defaults to 'template'
+ *
+ * @return The rendered view
+ */
+ function externalView(
+ required view,
+ struct args = {},
+ boolean cache = false,
+ cacheTimeout = "",
+ cacheLastAccessTimeout = "",
+ cacheSuffix = "",
+ cacheProvider = "template"
+ ){
+ return variables.controller.getRenderer().renderExternalView( argumentCollection = arguments );
+ }
+
+ /**
+ * Render a layout or a layout + view combo
+ *
+ * @layout The layout to render out
+ * @module The module to explicitly render this layout from
+ * @view The view to render within this layout
+ * @args An optional set of arguments that will be available to this layouts/view rendering ONLY
+ * @viewModule The module to explicitly render the view from
+ * @prePostExempt If true, pre/post layout interceptors will not be fired. By default they do fire
+ *
+ * @return The rendered layout
+ */
+ function layout(
+ layout,
+ module = "",
+ view = "",
+ struct args = {},
+ viewModule = "",
+ boolean prePostExempt = false
+ ){
+ return variables.controller.getRenderer().renderLayout( argumentCollection = arguments );
+ }
+
+ /**
+ * Announce an interception
+ *
+ * @state The interception state to announce
+ * @data A data structure used to pass intercepted information.
+ * @async If true, the entire interception chain will be ran in a separate thread.
+ * @asyncAll If true, each interceptor in the interception chain will be ran in a separate thread and then joined together at the end.
+ * @asyncAllJoin If true, each interceptor in the interception chain will be ran in a separate thread and joined together at the end by default. If you set this flag to false then there will be no joining and waiting for the threads to finalize.
+ * @asyncPriority The thread priority to be used. Either LOW, NORMAL or HIGH. The default value is NORMAL
+ * @asyncJoinTimeout The timeout in milliseconds for the join thread to wait for interceptor threads to finish. By default there is no timeout.
+ *
+ * @return struct of thread information or void
+ */
+ any function announce(
+ required state,
+ struct data = {},
+ boolean async = false,
+ boolean asyncAll = false,
+ boolean asyncAllJoin = true,
+ asyncPriority = "NORMAL",
+ numeric asyncJoinTimeout = 0
+ ){
+ // Backwards Compat: Remove by ColdBox 7
+ if ( !isNull( arguments.interceptData ) ) {
+ arguments.data = arguments.interceptData;
+ }
+ return variables.controller.getInterceptorService().announce( argumentCollection = arguments );
+ }
+
+ /**
+ * Executes events with full life-cycle methods and returns the event results if any were returned.
+ *
+ * @event The event string to execute, if nothing is passed we will execute the application's default event.
+ * @prePostExempt If true, pre/post handlers will not be fired. Defaults to false
+ * @private Execute a private event if set, else defaults to public events
+ * @defaultEvent The flag that let's this service now if it is the default event running or not. USED BY THE FRAMEWORK ONLY
+ * @eventArguments A collection of arguments to passthrough to the calling event handler method
+ * @cache Cached the output of the runnable execution, defaults to false. A unique key will be created according to event string + arguments.
+ * @cacheTimeout The time in minutes to cache the results
+ * @cacheLastAccessTimeout The time in minutes the results will be removed from cache if idle or requested
+ * @cacheSuffix The suffix to add into the cache entry for this event rendering
+ * @cacheProvider The provider to cache this event rendering in, defaults to 'template'
+ *
+ * @return null or anything produced from the event
+ */
+ function runEvent(
+ event = "",
+ boolean prePostExempt = false,
+ boolean private = false,
+ boolean defaultEvent = false,
+ struct eventArguments = {},
+ boolean cache = false,
+ cacheTimeout = "",
+ cacheLastAccessTimeout = "",
+ cacheSuffix = "",
+ cacheProvider = "template"
+ ){
+ return variables.controller.runEvent( argumentCollection = arguments );
+ }
+
+ /**
+ * Executes internal named routes with or without parameters. If the named route is not found or the route has no event to execute then this method will throw an `InvalidArgumentException`.
+ * If you need a route from a module then append the module address: `@moduleName` or prefix it like in run event calls `moduleName:routeName` in order to find the right route.
+ * The route params will be passed to events as action arguments much how eventArguments work.
+ *
+ * @name The name of the route
+ * @params The parameters of the route to replace
+ * @cache Cached the output of the runnable execution, defaults to false. A unique key will be created according to event string + arguments.
+ * @cacheTimeout The time in minutes to cache the results
+ * @cacheLastAccessTimeout The time in minutes the results will be removed from cache if idle or requested
+ * @cacheSuffix The suffix to add into the cache entry for this event rendering
+ * @cacheProvider The provider to cache this event rendering in, defaults to 'template'
+ * @prePostExempt If true, pre/post handlers will not be fired. Defaults to false
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return null or anything produced from the route
+ */
+ any function runRoute(
+ required name,
+ struct params = {},
+ boolean cache = false,
+ cacheTimeout = "",
+ cacheLastAccessTimeout = "",
+ cacheSuffix = "",
+ cacheProvider = "template",
+ boolean prePostExempt = false
+ ){
+ return variables.controller.runRoute( argumentCollection = arguments );
+ }
+
+ /**
+ * Get a named CacheBox Cache
+ *
+ * @name The name of the cache to retrieve, if not passed, it used the 'default' cache.
+ *
+ * @return coldbox.system.cache.providers.IColdBoxProvider
+ */
+ function getCache( name = "default" ){
+ return variables.controller.getCache( arguments.name );
+ }
+
+ /**
+ * Get a setting from the system
+ *
+ * @name The key of the setting
+ * @defaultValue If not found in config, default return value
+ *
+ * @throws SettingNotFoundException
+ *
+ * @return The requested setting
+ */
+ function getSetting( required name, defaultValue ){
+ return variables.controller.getSetting( argumentCollection = arguments );
+ }
+
+ /**
+ * Get a ColdBox setting
+ *
+ * @name The key to get
+ * @defaultValue The default value if it doesn't exist
+ *
+ * @throws SettingNotFoundException
+ *
+ * @return The framework setting value
+ */
+ function getColdBoxSetting( required name, defaultValue ){
+ return variables.controller.getColdBoxSetting( argumentCollection = arguments );
+ }
+
+ /**
+ * Check if the setting exists in the application
+ *
+ * @name The key of the setting
+ */
+ boolean function settingExists( required name ){
+ return variables.controller.settingExists( argumentCollection = arguments );
+ }
+
+ /**
+ * Set a new setting in the system
+ *
+ * @name The key of the setting
+ * @value The value of the setting
+ *
+ * @return FrameworkSuperType
+ */
+ any function setSetting( required name, required value ){
+ controller.setSetting( argumentCollection = arguments );
+ return this;
+ }
+
+ /**
+ * Get a module's settings structure or a specific setting if the setting key is passed
+ *
+ * @module The module to retrieve the configuration settings from
+ * @setting The setting to retrieve if passed
+ * @defaultValue The default value to return if setting does not exist
+ *
+ * @return struct or any
+ */
+ any function getModuleSettings( required module, setting, defaultValue ){
+ var moduleSettings = getModuleConfig( arguments.module ).settings;
+ // return specific setting?
+ if ( structKeyExists( arguments, "setting" ) ) {
+ return (
+ structKeyExists( moduleSettings, arguments.setting ) ? moduleSettings[ arguments.setting ] : arguments.defaultValue
+ );
+ }
+ return moduleSettings;
+ }
+
+ /**
+ * Get a module's configuration structure
+ *
+ * @module The module to retrieve the configuration structure from
+ *
+ * @throws InvalidModuleException - The module passed is invalid
+ *
+ * @return The struct requested
+ */
+ struct function getModuleConfig( required module ){
+ var mConfig = variables.controller.getSetting( "modules" );
+ if ( structKeyExists( mConfig, arguments.module ) ) {
+ return mConfig[ arguments.module ];
+ }
+ throw(
+ message = "The module you passed #arguments.module# is invalid.",
+ detail = "The loaded modules are #structKeyList( mConfig )#",
+ type = "InvalidModuleException"
+ );
+ }
+
+ /**
+ * Resolve a file to be either relative or absolute in your application
+ *
+ * @pathToCheck The file path to check
+ */
+ string function locateFilePath( required pathToCheck ){
+ return variables.controller.locateFilePath( argumentCollection = arguments );
+ }
+
+ /**
+ * Resolve a directory to be either relative or absolute in your application
+ *
+ * @pathToCheck The file path to check
+ */
+ string function locateDirectoryPath( required pathToCheck ){
+ return variables.controller.locateDirectoryPath( argumentCollection = arguments );
+ }
+
+ /**
+ * Retrieve a Java System property or env value by name. It looks at properties first then environment variables
+ *
+ * @key The name of the setting to look up.
+ * @defaultValue The default value to use if the key does not exist in the system properties or the env
+ */
+ function getSystemSetting( required key, defaultValue ){
+ return variables.controller.getUtil().getSystemSetting( argumentCollection = arguments );
+ }
+
+ /**
+ * Retrieve a Java System property only!
+ *
+ * @key The name of the setting to look up.
+ * @defaultValue The default value to use if the key does not exist in the system properties or the env
+ */
+ function getSystemProperty( required key, defaultValue ){
+ return variables.controller.getUtil().getSystemProperty( argumentCollection = arguments );
+ }
+
+ /**
+ * Retrieve a environment variable only
+ *
+ * @key The name of the setting to look up.
+ * @defaultValue The default value to use if the key does not exist in the system properties or the env
+ */
+ function getEnv( required key, defaultValue ){
+ return variables.controller.getUtil().getEnv( argumentCollection = arguments );
+ }
+
+}
diff --git a/test-harness/config/Scheduler.cfc b/test-harness/config/Scheduler.cfc
new file mode 100644
index 000000000..7500d26c3
--- /dev/null
+++ b/test-harness/config/Scheduler.cfc
@@ -0,0 +1,81 @@
+component {
+
+ function configure(){
+
+ task( "testharness-Heartbeat" )
+ .call( function() {
+ if ( randRange(1, 5) eq 1 ){
+ throw( message = "I am throwing up randomly!", type="RandomThrowup" );
+ }
+ writeDump( var='====> I am in a test harness test schedule!', output="console" );
+ } )
+ .every( "5", "seconds" )
+ .before( function( task ) {
+ writeDump( var='====> Running before the task!', output="console" );
+ } )
+ .after( function( task, results ){
+ writeDump( var='====> Running after the task!', output="console" );
+ } )
+ .onFailure( function( task, exception ){
+ writeDump( var='====> test schedule just failed!! #exception.message#', output="console" );
+ } )
+ .onSuccess( function( task, results ){
+ writeDump( var="====> Test scheduler success : Stats: #task.getStats().toString()#", output="console" );
+ } );
+
+ }
+
+ /**
+ * Called before the scheduler is going to be shutdown
+ */
+ function onShutdown(){
+ writeDump( var="Bye bye from the Global App Scheduler!", output="console" );
+ }
+
+ /**
+ * Called after the scheduler has registered all schedules
+ */
+ function onStartup(){
+ writeDump( var="The App Scheduler is in da house!!!!!", output="console" );
+ }
+
+ /**
+ * Called whenever ANY task fails
+ *
+ * @task The task that got executed
+ * @exception The ColdFusion exception object
+ */
+ function onAnyTaskError( required task, required exception ){
+ writeDump( var="the #arguments.task.getname()# task just went kabooooooom!", output="console" );
+ }
+
+ /**
+ * Called whenever ANY task succeeds
+ *
+ * @task The task that got executed
+ * @result The result (if any) that the task produced
+ */
+ function onAnyTaskSuccess( required task, result ){
+ writeDump( var="the #arguments.task.getname()# task completed!", output="console" );
+ }
+
+ /**
+ * Called before ANY task runs
+ *
+ * @task The task about to be executed
+ */
+ function beforeAnyTask( required task ){
+ writeDump( var="I am running before the task: #arguments.task.getName()#", output="console" );
+ }
+
+ /**
+ * Called after ANY task runs
+ *
+ * @task The task that got executed
+ * @result The result (if any) that the task produced
+ */
+ function afterAnyTask( required task, result ){
+ writeDump( var="I am running after the task: #arguments.task.getName()#", output="console" );
+ }
+
+}
\ No newline at end of file
diff --git a/test-harness/models/PhotosService.cfc b/test-harness/models/PhotosService.cfc
index e5ec304b4..29feb54cc 100644
--- a/test-harness/models/PhotosService.cfc
+++ b/test-harness/models/PhotosService.cfc
@@ -37,4 +37,8 @@ component singleton accessors="true" {
function get(){
}
+ function getRandom(){
+ return randRange( 1, 1000 );
+ }
+
}
diff --git a/test-harness/modules/resourcesTest/config/Scheduler.cfc b/test-harness/modules/resourcesTest/config/Scheduler.cfc
new file mode 100644
index 000000000..05a5322e1
--- /dev/null
+++ b/test-harness/modules/resourcesTest/config/Scheduler.cfc
@@ -0,0 +1,18 @@
+component {
+
+ property name="photosService" inject="PhotosService";
+
+ function configure(){
+
+ task( "photoNumbers" )
+ .call( function(){
+ var random = variables.photosService.getRandom();
+ writeDump( var="xxxxxxx> Photo numbers: #random#", output="console" );
+ return random;
+ } )
+ .every( 5, "seconds" )
+ .onEnvironment( "development" );
+
+ }
+
+}
\ No newline at end of file
diff --git a/tests/Application.cfc b/tests/Application.cfc
index d65bd916c..9d4649422 100755
--- a/tests/Application.cfc
+++ b/tests/Application.cfc
@@ -49,15 +49,13 @@ component{
public void function onRequestEnd( required targetPage ) {
- thread name="testbox-shutdown" {
- if( !isNull( application.cbController ) ){
- application.cbController.getLoaderService().processShutdown();
- }
-
- structDelete( application, "cbController" );
- structDelete( application, "wirebox" );
+ if( !isNull( application.cbController ) ){
+ application.cbController.getLoaderService().processShutdown();
}
+ structDelete( application, "cbController" );
+ structDelete( application, "wirebox" );
+
}
}
\ No newline at end of file
diff --git a/tests/index.cfm b/tests/index.cfm
index e27793c1a..2987b59fe 100644
--- a/tests/index.cfm
+++ b/tests/index.cfm
@@ -33,7 +33,7 @@
-
+
diff --git a/tests/specs/async/AsyncManagerSpec.cfc b/tests/specs/async/AsyncManagerSpec.cfc
index 55351e672..15405b30e 100644
--- a/tests/specs/async/AsyncManagerSpec.cfc
+++ b/tests/specs/async/AsyncManagerSpec.cfc
@@ -14,6 +14,15 @@ component extends="BaseAsyncSpec" {
asyncManager = new coldbox.system.async.AsyncManager( debug = true );
} );
+ it( "can produce durations", function(){
+ expect(
+ asyncManager
+ .duration()
+ .of( 10 )
+ .get()
+ ).toBe( 10 );
+ } );
+
it( "can run a cf closure with a then/get pipeline and custom executors", function(){
var singlePool = asyncManager.$executors.newFixedThreadPool( 1 );
var f = asyncManager
@@ -173,7 +182,7 @@ component extends="BaseAsyncSpec" {
debug( "calculating BMI" );
var combinedFuture = weightFuture.thenCombine( heightFuture, function( weight, height ){
- writeDump( var = arguments, output="console" );
+ writeDump( var = arguments, output = "console" );
var heightInMeters = arguments.height / 100;
return arguments.weight / ( heightInMeters * heightInMeters );
} );
diff --git a/tests/specs/async/util/ExecutorsSpec.cfc b/tests/specs/async/executors/ExecutorsSpec.cfc
similarity index 93%
rename from tests/specs/async/util/ExecutorsSpec.cfc
rename to tests/specs/async/executors/ExecutorsSpec.cfc
index b9e780b01..af671ff84 100644
--- a/tests/specs/async/util/ExecutorsSpec.cfc
+++ b/tests/specs/async/executors/ExecutorsSpec.cfc
@@ -9,7 +9,7 @@ component extends="tests.specs.async.BaseAsyncSpec" {
// all your suites go here.
describe( "Executors", function(){
beforeEach( function( currentSpec ){
- executors = new coldbox.system.async.util.Executors();
+ executors = new coldbox.system.async.executors.ExecutorBuilder();
} );
it( "can be created", function(){
diff --git a/tests/specs/async/tasks/ColdBoxScheduledTaskSpec.cfc b/tests/specs/async/tasks/ColdBoxScheduledTaskSpec.cfc
new file mode 100644
index 000000000..2666e4b01
--- /dev/null
+++ b/tests/specs/async/tasks/ColdBoxScheduledTaskSpec.cfc
@@ -0,0 +1,84 @@
+component extends="tests.resources.BaseIntegrationTest" {
+
+ /*********************************** BDD SUITES ***********************************/
+
+ function beforeAll(){
+ variables.asyncManager = new coldbox.system.async.AsyncManager();
+ super.beforeAll();
+ }
+
+ function run( testResults, testBox ){
+ // all your suites go here.
+ describe( "ColdBox Scheduled Task", function(){
+ beforeEach( function( currentSpec ){
+ variables.scheduler = new coldbox.system.web.tasks.ColdBoxScheduler(
+ "bdd-tests",
+ variables.asyncManager,
+ getController()
+ );
+ } );
+
+ afterEach( function( currentSpec ){
+ getCache( "template" ).clearAll();
+ variables.scheduler.shutdown();
+ } );
+
+ it( "can register a ColdBox enhanced task", function(){
+ var t = scheduler.task( "cbTask" );
+ expect( t.hasScheduler() ).toBeTrue();
+ expect( t.getCacheName() ).toBe( "template" );
+ expect( t.getServerFixation() ).toBeFalse();
+ expect( scheduler.getTaskRecord( "cbTask" ).task ).toBe( t );
+ } );
+
+ it( "can register environment constraints", function(){
+ var t = scheduler.task( "cbTask" ).onEnvironment( "development" );
+ expect( t.getEnvironments() ).toInclude( "development" );
+ } );
+
+ it( "can register server fixation", function(){
+ var t = scheduler.task( "cbTask" ).onOneServer();
+ expect( t.getServerFixation() ).toBeTrue();
+ } );
+
+ it( "can be constrained by environment", function(){
+ var t = scheduler.task( "cbTask" );
+
+ expect( t.isConstrained() ).toBeFalse();
+
+ // Constrain it
+ t.onEnvironment( "bogus" );
+ expect( t.getEnvironments() ).toInclude( "bogus" );
+ expect( t.isConstrained() ).toBeTrue();
+ } );
+
+ it( "can be constrained by server", function(){
+ var t = scheduler.task( "cbTask" ).onOneServer();
+ var cacheKey = "cbtasks-server-fixation-#replace( t.getName(), " ", "-", "all" )#";
+
+ expect( t.isConstrained() ).toBeFalse();
+ expect( t.getCache().getKeys() ).toInclude( cacheKey );
+
+ // Constrain it
+ t.cleanupTaskRun();
+ expect( t.getCache().getKeys() ).notToInclude( cacheKey );
+ t.getCache()
+ .set(
+ cacheKey,
+ {
+ "task" : t.getName(),
+ "lockOn" : now(),
+ "serverHost" : "10.10.10.10",
+ "serverIp" : "my.macdaddy.bogus.bdd.server"
+ },
+ 5,
+ 0
+ );
+ expect( t.getCache().getKeys() ).toInclude( cacheKey );
+ expect( t.isConstrained() ).toBeTrue();
+ t.cleanupTaskRun();
+ } );
+ } );
+ }
+
+}
diff --git a/tests/specs/async/tasks/ScheduledExecutorSpec.cfc b/tests/specs/async/tasks/ScheduledExecutorSpec.cfc
index f85b3da35..171374d2c 100644
--- a/tests/specs/async/tasks/ScheduledExecutorSpec.cfc
+++ b/tests/specs/async/tasks/ScheduledExecutorSpec.cfc
@@ -135,14 +135,14 @@ component extends="tests.specs.async.BaseAsyncSpec" {
} );
} );
-
story( "Ability to use a builder to schedule tasks", function(){
it( "can use the builder to schedule a one-time task", function(){
var scheduler = asyncManager.newScheduledExecutor( "myExecutor" );
var atomicLong = createObject( "java", "java.util.concurrent.atomic.AtomicLong" ).init( 0 );
var sFuture = scheduler
- .newSchedule( function(){
+ .newTask( "autoIncrementTask" )
+ .call( function(){
var results = atomicLong.incrementAndGet();
toConsole( "running periodic task (#results#) from:#getThreadName()#" );
} )
diff --git a/tests/specs/async/tasks/ScheduledTaskSpec.cfc b/tests/specs/async/tasks/ScheduledTaskSpec.cfc
new file mode 100644
index 000000000..624468b59
--- /dev/null
+++ b/tests/specs/async/tasks/ScheduledTaskSpec.cfc
@@ -0,0 +1,339 @@
+component extends="tests.specs.async.BaseAsyncSpec" {
+
+ /*********************************** BDD SUITES ***********************************/
+
+ function beforeAll(){
+ variables.asyncManager = new coldbox.system.async.AsyncManager();
+ super.beforeAll();
+ }
+
+ function run( testResults, testBox ){
+ // all your suites go here.
+ describe( "Scheduled Task", function(){
+ beforeEach( function( currentSpec ){
+ variables.scheduler = asyncManager.newScheduler( "bdd-test" ).setTimezone( "America/Chicago" );
+ } );
+
+ afterEach( function( currentSpec ){
+ variables.scheduler.shutdown();
+ } );
+
+ it( "can be created", function(){
+ var t = scheduler.task( "test" );
+ expect( t.getName() ).toBe( "test" );
+ expect( t.hasScheduler() ).toBeTrue();
+ } );
+
+ it( "can have truth based restrictions using when()", function(){
+ var t = scheduler
+ .task( "test" )
+ .when( function(){
+ return true;
+ } );
+ expect( isClosure( t.getWhen() ) ).toBeTrue();
+ } );
+
+ it( "can set a timezone", function(){
+ var t = scheduler.task( "test" ).setTimezone( "America/Chicago" );
+ expect( t.getTimezone().toString() ).toBe( "America/Chicago" );
+ } );
+
+ it( "can be disabled", function(){
+ var t = scheduler.task( "test" );
+ expect( t.isDisabled() ).toBeFalse();
+ expect( t.disable().isDisabled() ).toBeTrue();
+ } );
+
+ describe( "can have life cycle methods", function(){
+ it( "can call before", function(){
+ var t = scheduler
+ .task( "test" )
+ .before( function(){
+ return true;
+ } );
+ expect( isClosure( t.getBeforeTask() ) ).toBeTrue();
+ } );
+ it( "can call after", function(){
+ var t = scheduler
+ .task( "test" )
+ .after( function(){
+ return true;
+ } );
+ expect( isClosure( t.getafterTask() ) ).toBeTrue();
+ } );
+ it( "can call onTaskSuccess", function(){
+ var t = scheduler
+ .task( "test" )
+ .onSuccess( function(){
+ return true;
+ } );
+ expect( isClosure( t.getonTaskSuccess() ) ).toBeTrue();
+ } );
+ it( "can call onTaskFailure", function(){
+ var t = scheduler
+ .task( "test" )
+ .onFailure( function(){
+ return true;
+ } );
+ expect( isClosure( t.getonTaskFailure() ) ).toBeTrue();
+ } );
+ } );
+
+ describe( "can register multiple frequencies using everyXXX() method calls", function(){
+ var timeUnits = [
+ "days",
+ "hours",
+ "microseconds",
+ "milliseconds",
+ "minutes",
+ "nanoseconds",
+ "seconds"
+ ];
+
+ timeUnits.each( function( thisUnit ){
+ it(
+ title = "can register every 5 #thisUnit# as a time unit",
+ body = function( data ){
+ var t = scheduler.task( "test" ).every( 5, data.unit );
+ expect( t.getPeriod() ).toBe( 5 );
+ expect( t.getTimeUnit() ).toBe( data.unit );
+ },
+ data = { unit : thisUnit }
+ );
+ } );
+
+ it( "can register using everyMinute()", function(){
+ var t = scheduler.task( "test" ).everyMinute();
+ expect( t.getPeriod() ).toBe( 1 );
+ expect( t.getTimeUnit() ).toBe( "minutes" );
+ } );
+
+ it( "can register everyHour()", function(){
+ var t = scheduler.task( "test" ).everyHour();
+ expect( t.getPeriod() ).toBe( 1 );
+ expect( t.getTimeUnit() ).toBe( "hours" );
+ } );
+
+ it( "can register everyHourAt()", function(){
+ var t = scheduler.task( "test" ).everyHourAt( 15 );
+ expect( t.getDelay() ).notToBeEmpty();
+ expect( t.getPeriod() ).toBe( 3600 );
+ expect( t.getTimeUnit() ).toBe( "seconds" );
+ } );
+
+ it( "can register everyDay()", function(){
+ var t = scheduler.task( "test" ).everyDay();
+ expect( t.getPeriod() ).toBe( 86400 );
+ expect( t.getTimeUnit() ).toBe( "seconds" );
+ } );
+
+ it( "can register everyDayAt()", function(){
+ var t = scheduler.task( "test" ).everyDayAt( "04:00" );
+ expect( t.getDelay() ).notToBeEmpty();
+ expect( t.getPeriod() ).toBe( 86400 );
+ expect( t.getTimeUnit() ).toBe( "seconds" );
+ } );
+
+ it( "can register everyWeek()", function(){
+ var t = scheduler.task( "test" ).everyWeek();
+ expect( t.getPeriod() ).toBe( 604800 );
+ expect( t.getTimeUnit() ).toBe( "seconds" );
+ } );
+
+ it( "can register everyWeekOn()", function(){
+ var t = scheduler.task( "test" ).everyWeekOn( 4, "09:00" );
+ expect( t.getPeriod() ).toBe( 604800 );
+ expect( t.getTimeUnit() ).toBe( "seconds" );
+ } );
+
+ it( "can register everyMonth()", function(){
+ var t = scheduler.task( "test" ).everyMonth();
+ expect( t.getPeriod() ).toBe( 86400 );
+ expect( t.getTimeUnit() ).toBe( "seconds" );
+ } );
+
+ it( "can register everyMonthOn()", function(){
+ var t = scheduler.task( "test" ).everyMonthOn( 10, "09:00" );
+ expect( t.getPeriod() ).toBe( 86400 );
+ expect( t.getTimeUnit() ).toBe( "seconds" );
+ } );
+
+ it( "can register everyYear()", function(){
+ var t = scheduler.task( "test" ).everyYear();
+ expect( t.getPeriod() ).toBe( 31536000 );
+ expect( t.getTimeUnit() ).toBe( "seconds" );
+ } );
+
+ it( "can register everyYearOn()", function(){
+ var t = scheduler.task( "test" ).everyYearOn( 4, 15, "09:00" );
+ expect( t.getPeriod() ).toBe( 31536000 );
+ expect( t.getTimeUnit() ).toBe( "seconds" );
+ } );
+ } );
+
+ describe( "can register frequencies with constraints", function(){
+ it( "can register to fire onFirstBusinessDayOfTheMonth()", function(){
+ var t = scheduler.task( "test" ).onFirstBusinessDayOfTheMonth( "09:00" );
+ expect( t.getPeriod() ).toBe( 86400 );
+ expect( t.getTimeUnit() ).toBe( "seconds" );
+ expect( t.getDayOfTheMonth() ).toBe( 1 );
+ } );
+
+ it( "can register to fire onLastBusinessDayOfTheMonth()", function(){
+ var t = scheduler.task( "test" ).onLastBusinessDayOfTheMonth( "09:00" );
+ expect( t.getPeriod() ).toBe( 86400 );
+ expect( t.getTimeUnit() ).toBe( "seconds" );
+ expect( t.getLastBusinessDay() ).toBeTrue();
+ } );
+
+
+ it( "can register to fire onWeekends()", function(){
+ var t = scheduler.task( "test" ).onWeekends( "09:00" );
+ expect( t.getPeriod() ).toBe( 86400 );
+ expect( t.getTimeUnit() ).toBe( "seconds" );
+ expect( t.getWeekends() ).toBeTrue();
+ expect( t.getWeekdays() ).toBeFalse();
+ } );
+
+ it( "can register to fire onWeekdays()", function(){
+ var t = scheduler.task( "test" ).onWeekdays( "09:00" );
+ expect( t.getPeriod() ).toBe( 86400 );
+ expect( t.getTimeUnit() ).toBe( "seconds" );
+ expect( t.getWeekends() ).toBeFalse();
+ expect( t.getWeekdays() ).toBeTrue();
+ } );
+
+ var daysOfTheWeek = [
+ "mondays",
+ "tuesdays",
+ "wednesdays",
+ "thursdays",
+ "fridays",
+ "saturdays",
+ "sundays"
+ ];
+ daysOfTheWeek.each( function( thisDay ){
+ it(
+ title = "can register to fire on the #thisDay#",
+ body = function( data ){
+ var t = scheduler.task( "test" );
+
+ invoke( t, "on#data.thisDay#" );
+
+ expect( t.getPeriod() ).toBe( 604800 );
+ expect( t.getTimeUnit() ).toBe( "seconds" );
+ },
+ data = { thisDay : thisDay }
+ );
+ } );
+ } );
+
+ it( "can register tasks with no overlaps", function(){
+ var t = scheduler
+ .task( "test" )
+ .everyMinute()
+ .withNoOverlaps();
+ expect( t.getPeriod() ).toBe( 1 );
+ expect( t.getNoOverlaps() ).toBeTrue();
+ expect( t.getTimeUnit() ).toBe( "minutes" );
+ } );
+
+ describe( "can have multiple constraints", function(){
+ it( "can have a truth value constraint", function(){
+ var t = scheduler
+ .task( "test" )
+ .when( function(){
+ return false;
+ } );
+ expect( t.isConstrained() ).toBeTrue();
+ } );
+
+ it( "can have a day of the month constraint", function(){
+ var t = scheduler.task( "test" );
+ t.setDayOfTheMonth( day( dateAdd( "d", 1, now() ) ) );
+ expect( t.isConstrained() ).toBeTrue();
+
+ t.setDayOfTheMonth( day( now() ) );
+ expect( t.isConstrained() ).toBeFalse();
+ } );
+
+ it( "can have a last business day of the month constraint", function(){
+ var t = scheduler.task( "test" ).setLastBusinessDay( true );
+ expect( t.isConstrained() ).toBeTrue();
+
+ var mockNow = t.getJavaNow();
+ prepareMock( t ).$( "getLastDayOfTheMonth", mockNow );
+
+ expect( t.isConstrained() ).toBeFalse();
+ } );
+
+ it( "can have a day of the week constraint", function(){
+ var t = scheduler.task( "test" );
+ var mockNow = t.getJavaNow();
+ // Reduce date enough to do computations on it
+ if ( mockNow.getDayOfWeek().getValue() > 6 ) {
+ mockNow = mockNow.minusDays( javacast( "long", 3 ) );
+ }
+
+ t.setDayOfTheWeek( mockNow.getDayOfWeek().getValue() + 1 );
+ expect( t.isConstrained() ).toBeTrue( "Constrained!!" );
+
+ t.setDayOfTheWeek(
+ t.getJavaNow()
+ .getDayOfWeek()
+ .getValue()
+ );
+ expect( t.isConstrained() ).toBeFalse( "Should execute" );
+ } );
+
+ it( "can have a weekend constraint", function(){
+ var t = scheduler.task( "test" ).setWeekends( true );
+
+ // build a weekend date
+ var mockNow = t.getJavaNow();
+ var dayOfWeek = mockNow.getDayOfWeek().getValue();
+
+ if ( dayOfWeek < 6 ) {
+ mockNow = mockNow.plusDays( javacast( "long", 6 - dayOfWeek ) );
+ }
+
+ prepareMock( t ).$( "getJavaNow", mockNow );
+ expect( t.isConstrained() ).toBeFalse(
+ "Weekend day (#mockNow.getDayOfWeek().getvalue()#) should pass"
+ );
+
+ // Test non weekend
+ mockNow = mockNow.minusDays( javacast( "long", 3 ) );
+ t.$( "getJavaNow", mockNow );
+ expect( t.isConstrained() ).toBeTrue(
+ "Weekday (#mockNow.getDayOfWeek().getvalue()#) should be constrained"
+ );
+ } );
+
+ it( "can have a weekday constraint", function(){
+ var t = scheduler.task( "test" ).setWeekdays( true );
+
+ // build a weekday date
+ var mockNow = t.getJavaNow();
+ var dayOfWeek = mockNow.getDayOfWeek().getValue();
+ if ( dayOfWeek >= 6 ) {
+ mockNow = mockNow.minusDays( javacast( "long", 3 ) );
+ }
+
+ prepareMock( t ).$( "getJavaNow", mockNow );
+ expect( t.isConstrained() ).toBeFalse(
+ "Weekday (#mockNow.getDayOfWeek().getvalue()#) should pass"
+ );
+
+ // Test weekend
+ mockNow = mockNow.plusDays( javacast( "long", 6 - mockNow.getDayOfWeek().getValue() ) );
+ t.$( "getJavaNow", mockNow );
+ expect( t.isConstrained() ).toBeTrue(
+ "Weekend (#mockNow.getDayOfWeek().getvalue()#) should be constrained"
+ );
+ } );
+ } );
+ } );
+ }
+
+}
diff --git a/tests/specs/async/tasks/SchedulerSpec.cfc b/tests/specs/async/tasks/SchedulerSpec.cfc
new file mode 100644
index 000000000..74f6ec88c
--- /dev/null
+++ b/tests/specs/async/tasks/SchedulerSpec.cfc
@@ -0,0 +1,135 @@
+/**
+ * Duration Specs
+ */
+component extends="tests.specs.async.BaseAsyncSpec" {
+
+ /*********************************** BDD SUITES ***********************************/
+
+ function run( testResults, testBox ){
+ // all your suites go here.
+ describe( "Scheduler Spec", function(){
+ beforeEach( function( currentSpec ){
+ asyncManager = new coldbox.system.async.AsyncManager();
+ scheduler = asyncManager.newScheduler( "bdd-test" );
+ } );
+
+ it( "can be created", function(){
+ expect( scheduler ).toBeComponent();
+ expect( scheduler.getName() ).toBe( "bdd-test" );
+ expect( scheduler.getExecutor() ).toBeComponent();
+ expect( scheduler.getExecutor().getName() ).toBe( "bdd-test-scheduler" );
+ } );
+
+ it( "can set a new timezone", function(){
+ scheduler.setTimezone( "America/New_York" );
+ expect( scheduler.getTimezone().toString() ).toBe( "America/New_York" );
+ } );
+
+ it( "can register a new task and get it's record", function(){
+ var task = scheduler.task( "bddTest" );
+ expect( scheduler.hasTask( "bddTest" ) ).toBeTrue();
+ expect( scheduler.getRegisteredTasks() ).toInclude( "bddTest" );
+ expect( scheduler.getTaskRecord( "bddTest" ).task.getName() ).toBe( "bddTest" );
+ } );
+
+ it( "can throw an exception on getting a bogus task record", function(){
+ expect( function(){
+ scheduler.getTaskRecord( "bogus" );
+ } ).toThrow();
+ } );
+
+ it( "can remove a task", function(){
+ var task = scheduler.task( "bddTest" );
+ expect( scheduler.hasTask( "bddTest" ) ).toBeTrue();
+ scheduler.removeTask( "bddTest" );
+ expect( scheduler.hasTask( "bddTest" ) ).toBeFalse();
+ } );
+
+ it( "can throw an exception on removing a non-existent task", function(){
+ expect( function(){
+ scheduler.removeTask( "bogus" );
+ } ).toThrow();
+ } );
+
+ it( "can register and run the tasks with life cycle methods", function(){
+ var atomicLong = createObject( "java", "java.util.concurrent.atomic.AtomicLong" ).init( 0 );
+
+ // Register two one-time tasks
+ scheduler
+ .task( "test1" )
+ .call( function(){
+ var results = atomicLong.incrementAndGet();
+ toConsole( "Running test1 (#results#) from:#getThreadName()#" );
+ return results;
+ } )
+ .before( function( task ){
+ toConsole( "I am about to run baby!" );
+ } )
+ .after( function( task, results ){
+ toConsole( "this task is done baby!" );
+ } )
+ .onFailure( function( task, exception ){
+ toConsole( "we blew up: #exception.message#" );
+ } )
+ .onSuccess( function( task, results ){
+ toConsole( "Task has completed with success baby! #results#" );
+ } );
+
+ scheduler
+ .task( "test2" )
+ .call( function(){
+ var results = atomicLong.incrementAndGet();
+ toConsole( "Running test2 (#results#) from:#getThreadName()#" );
+ } );
+
+ scheduler
+ .task( "test3" )
+ .call( function(){
+ var results = atomicLong.incrementAndGet();
+ toConsole( "Running test3 (#results#) from:#getThreadName()#" );
+ } )
+ .disable();
+
+ // Startup the scheduler
+ try {
+ expect( scheduler.hasStarted() ).toBeFalse();
+ scheduler.startup();
+ expect( scheduler.hasStarted() ).toBeTrue();
+
+ var record = scheduler.getTaskRecord( "test1" );
+ expect( record.future ).toBeComponent();
+ expect( record.scheduledAt ).notToBeEmpty();
+
+ var record = scheduler.getTaskRecord( "test2" );
+ expect( record.future ).toBeComponent();
+ expect( record.scheduledAt ).notToBeEmpty();
+
+ var record = scheduler.getTaskRecord( "test3" );
+ expect( record.disabled ).toBeTrue();
+ expect( record.future ).toBeEmpty();
+ expect( record.scheduledAt ).toBeEmpty();
+
+ // Wait for them to execute
+ sleep( 1000 );
+ var stats = scheduler.getTaskStats();
+
+ debug( scheduler.getTasks() );
+ debug( stats );
+
+ expect( stats.test1.neverRun ).toBeFalse( "test 1 neverRun" );
+ expect( stats.test2.neverRun ).toBeFalse( "test 2 neverRun" );
+ expect( stats.test3.neverRun ).toBeTrue( "test 3 neverRun" );
+
+ expect( stats.test1.totalRuns ).toBe( 1, "test1 totalRuns" );
+ expect( stats.test2.totalRuns ).toBe( 1, "test2 totalRuns" );
+ expect( stats.test3.totalRuns ).toBe( 0, "test3 totalRuns" );
+ } finally {
+ sleep( 1000 );
+ scheduler.shutdown();
+ expect( scheduler.hasStarted() ).toBeFalse( "Final scheduler stopped" );
+ }
+ } );
+ } );
+ }
+
+}
diff --git a/tests/specs/async/time/ChronoUnitSpec.cfc b/tests/specs/async/time/ChronoUnitSpec.cfc
new file mode 100644
index 000000000..0aba2dd78
--- /dev/null
+++ b/tests/specs/async/time/ChronoUnitSpec.cfc
@@ -0,0 +1,76 @@
+/**
+ * Duration Specs
+ */
+component extends="tests.specs.async.BaseAsyncSpec" {
+
+ /*********************************** BDD SUITES ***********************************/
+
+ function run( testResults, testBox ){
+ // all your suites go here.
+ describe( "Chrono Units", function(){
+ beforeEach( function( currentSpec ){
+ chronounit = new coldbox.system.async.time.ChronoUnit();
+ } );
+
+ it( "can be created", function(){
+ expect( chronounit ).toBeComponent();
+ } );
+
+ it( "can convert cf dates to java instants", function(){
+ var instant = chronounit.toInstant( now() );
+ expect( instant.getEpochSecond() ).toBeGT( 0 );
+
+ var instant = chronounit.toInstant( "2021-01-01 12:00:00 pm" );
+ expect( instant.getEpochSecond() ).toBeGT( 0 );
+ } );
+
+ it( "can convert cf dates to Java LocalDates", function(){
+ var jdate = chronounit.toLocalDate( now() );
+ expect( jDate.getYear() ).toBe( year( now() ) );
+ var jdate = chronounit.toLocalDate( now(), "America/New_York" );
+ expect( jDate.getYear() ).toBe( year( now() ) );
+ } );
+
+ it( "can get timezones", function(){
+ var t = chronounit.getTimezone( "America/Chicago" );
+ expect( t.getId() ).toInclude( "America/Chicago" );
+ } );
+
+ it( "can get the system timezone id", function(){
+ var t = chronounit.getSystemTimezone();
+ expect( t.getId() ).notToBeEmpty();
+ } );
+
+ var units = [
+ "CENTURIES",
+ "DAYS",
+ "DECADES",
+ "ERAS",
+ "FOREVER",
+ "HALF_DAYS",
+ "HOURS",
+ "MICROS",
+ "MILLENNIA",
+ "MILLIS",
+ "MINUTES",
+ "MONTHS",
+ "NANOS",
+ "SECONDS",
+ "WEEKS",
+ "YEARS"
+ ];
+
+ units.each( function( thisUnit ){
+ it(
+ data = { unit : thisUnit },
+ title = "can produce the #thisUnit# java chrono unit",
+ body = function( data ){
+ var unit = chronoUnit[ data.unit ];
+ expect( unit.toString() ).toInclude( data.unit.replace( "_", "" ) );
+ }
+ );
+ } );
+ } );
+ }
+
+}
diff --git a/tests/specs/async/time/DurationSpec.cfc b/tests/specs/async/time/DurationSpec.cfc
new file mode 100644
index 000000000..e5c87a791
--- /dev/null
+++ b/tests/specs/async/time/DurationSpec.cfc
@@ -0,0 +1,107 @@
+/**
+ * Duration Specs
+ */
+component extends="tests.specs.async.BaseAsyncSpec" {
+
+ /*********************************** BDD SUITES ***********************************/
+
+ function run( testResults, testBox ){
+ // all your suites go here.
+ describe( "Duration", function(){
+ beforeEach( function( currentSpec ){
+ duration = new coldbox.system.async.time.Duration();
+ } );
+
+ it( "can be created", function(){
+ expect( duration ).toBeComponent();
+ } );
+
+ it( "can do creations with of methods", function(){
+ expect( duration.ofDays( 7 ).toString() ).toBe( "PT168H" );
+ expect( duration.ofHours( 8 ).toString() ).toBe( "PT8H" );
+ expect( duration.ofMinutes( 15 ).toString() ).toBe( "PT15M" );
+ expect( duration.ofSeconds( 10 ).toString() ).toBe( "PT10S" );
+ expect( duration.ofSeconds( 30, 123456789 ).toString() ).toBe( "PT30.123456789S" );
+ expect( duration.between( "2017-10-03T10:15:30.00Z", "2017-10-03T10:16:30.00Z" ).getSeconds() ).toBe(
+ 60
+ );
+ expect( duration.between( "2021-01-01 00:00:00", "2021-01-01 00:00:01" ).toString() ).toBe(
+ "PT1S"
+ );
+ expect(
+ duration
+ .between( createDateTime( 2019, 1, 1, 0, 0, 0 ), createDateTime( 2021, 1, 1, 0, 0, 0 ) )
+ .toString()
+ ).toBe( "PT17544H" );
+ } );
+
+ it( "can parse strings into durations", function(){
+ var d = duration.parse( "P1DT8H15M10.345000S" );
+ expect( d.getSeconds() ).toBe( 116110 );
+ expect( d.getNano() ).toBe( 345000000 );
+ expect( d.getUnits() ).toInclude( "seconds" ).toInclude( "nanos" );
+ } );
+
+ it( "can check utility methods", function(){
+ expect( duration.iszero() ).toBeTrue();
+ expect( duration.get() ).toBe( 0 );
+ expect( duration.getSeconds() ).toBe( 0 );
+ expect( duration.getNano() ).toBe( 0 );
+ expect( duration.isNegative() ).toBeFalse();
+ } );
+
+ it( "can do minus", function(){
+ var d = duration.of( 10 );
+ expect( duration.minus( 1 ).get() ).toBe( 9 );
+ } );
+
+ it( "can do plus", function(){
+ var d = duration.of( 10 );
+ expect( duration.plus( 1 ).get() ).toBe( 11 );
+ } );
+
+ it( "can do multiplication", function(){
+ var d = duration.of( 10 );
+ expect( duration.multipliedBy( 10 ).get() ).toBe( 100 );
+ } );
+
+ it( "can do division", function(){
+ var d = duration.of( 10 );
+ expect( duration.dividedBy( 10 ).get() ).toBe( 1 );
+ } );
+
+ it( "can do negations", function(){
+ var d = duration.of( -1 );
+ expect( d.negated().isNegative() ).toBeFalse();
+
+ var d = duration.of( -1 );
+ expect( d.isNegative() ).toBeTrue();
+ expect( d.abs().isNegative() ).toBeFalse();
+ } );
+
+ it( "can create it from another duration", function(){
+ var test = duration.ofSeconds( 5 );
+ expect( duration.from( test ).get() ).toBe( 5 );
+ } );
+
+ it( "can convert to other time units", function(){
+ expect( duration.ofMinutes( 60 ).toHours() ).toBe( 1 );
+ expect( duration.ofMinutes( 60 ).toSeconds() ).toBe( 60 * 60 );
+ expect( duration.ofMinutes( 60 ).toMillis() ).toBe( 60 * 60 * 1000 );
+ } );
+
+ it( "can add durations to date/time objects", function(){
+ var now = now();
+ var t = duration.ofDays( 10 ).addTo( now );
+ expect( dateDiff( "d", now, t ) ).toBe( 10 );
+ } );
+
+ it( "can subtract durations to date/time objects", function(){
+ var now = now();
+ var t = duration.ofDays( 10 ).subtractFrom( now );
+ expect( dateDiff( "d", now, t ) ).toBe( -10 );
+ } );
+ } );
+ }
+
+}
diff --git a/tests/specs/async/time/PeriodSpec.cfc b/tests/specs/async/time/PeriodSpec.cfc
new file mode 100644
index 000000000..273743863
--- /dev/null
+++ b/tests/specs/async/time/PeriodSpec.cfc
@@ -0,0 +1,160 @@
+/**
+ * period Specs
+ */
+component extends="tests.specs.async.BaseAsyncSpec" {
+
+ /*********************************** BDD SUITES ***********************************/
+
+ function run( testResults, testBox ){
+ // all your suites go here.
+ describe( "Period", function(){
+ beforeEach( function( currentSpec ){
+ period = new coldbox.system.async.time.Period();
+ } );
+
+ it( "can be created", function(){
+ expect( period ).toBeComponent();
+ } );
+
+ it( "can do creations with of methods", function(){
+ var p = period.of();
+ expect( p.getyears() ).toBe( 0 );
+ expect( p.getMonths() ).toBe( 0 );
+ expect( p.getDays() ).toBe( 0 );
+
+ var p = period.of( 1, 5, 2 );
+ expect( p.getyears() ).toBe( 1 );
+ expect( p.getMonths() ).toBe( 5 );
+ expect( p.getDays() ).toBe( 2 );
+ } );
+
+ it( "can build days", function(){
+ expect( period.ofDays( 10 ).getDays() ).toBe( 10 );
+ } );
+
+ it( "can build months", function(){
+ expect( period.ofMonths( 10 ).getMonths() ).toBe( 10 );
+ } );
+
+ it( "can build weeks", function(){
+ var p = period.ofWeeks( 2 );
+ expect( p.getDays() ).toBe( 14 );
+ } );
+
+ it( "can build years", function(){
+ expect( period.ofyears( 10 ).getyears() ).toBe( 10 );
+ } );
+
+ it( "can parse strings into periods", function(){
+ var p = period.parse( "P2Y" );
+ expect( p.getYears() ).toBe( 2 );
+
+ var p = period.parse( "P2Y5M" );
+ expect( p.getYears() ).toBe( 2 );
+ expect( p.getMonths() ).toBe( 5 );
+
+ var p = period.parse( "P2Y5M10D" );
+ expect( p.getYears() ).toBe( 2 );
+ expect( p.getMonths() ).toBe( 5 );
+ expect( p.getDays() ).toBe( 10 );
+ } );
+
+ it( "can check utility methods", function(){
+ expect( period.iszero() ).toBeTrue();
+ expect( period.get() ).toBe( 0 );
+ expect( period.isNegative() ).toBeFalse();
+ } );
+
+ it( "can get total months", function(){
+ var p = period.parse( "P10Y5M20D" );
+ expect( p.toTotalMonths() ).toBe( 125 );
+ } );
+
+ it( "can do minus", function(){
+ expect(
+ period
+ .of( 5 )
+ .minus( period.ofDays( 5 ) )
+ .getDays()
+ ).toBe( 0 );
+
+ expect(
+ period
+ .init()
+ .ofDays( 5 )
+ .minusDays( 1 )
+ .getDays()
+ ).toBe( 4 );
+
+ expect(
+ period
+ .init()
+ .ofMonths( 5 )
+ .minusMonths( 1 )
+ .getMonths()
+ ).toBe( 4 );
+
+ expect(
+ period
+ .init()
+ .ofYears( 5 )
+ .minusYears( 1 )
+ .getYears()
+ ).toBe( 4 );
+ } );
+
+ it( "can do plus", function(){
+ expect(
+ period
+ .of( 5 )
+ .plus( period.ofDays( 5 ) )
+ .getDays()
+ ).toBe( 10 );
+
+ expect(
+ period
+ .ofDays( 5 )
+ .plusDays( 1 )
+ .getDays()
+ ).toBe( 6 );
+
+ expect(
+ period
+ .init()
+ .ofMonths( 5 )
+ .plusMonths( 1 )
+ .getMonths()
+ ).toBe( 6 );
+
+ expect(
+ period
+ .init()
+ .ofYears( 5 )
+ .plusYears( 1 )
+ .getYears()
+ ).toBe( 6 );
+ } );
+
+ it( "can do multiplication", function(){
+ var d = period.ofDays( 10 );
+ expect( period.multipliedBy( 10 ).getDays() ).toBe( 100 );
+ } );
+
+ it( "can do negations", function(){
+ var d = period.ofDays( -1 );
+ expect( d.negated().isNegative() ).toBeFalse();
+
+ var d = period.ofDays( -1 );
+ expect( d.isNegative() ).toBeTrue();
+ } );
+
+ it( "can addTo", function(){
+ var p = period.of( 2, 5, 10 );
+ var target = "2021-01-01";
+
+ expect( p.addTo( target ) ).tobe( "2023-06-11" );
+ } );
+ } );
+ }
+
+}
diff --git a/tests/specs/async/util/TimeUnitSpec.cfc b/tests/specs/async/time/TimeUnitSpec.cfc
similarity index 93%
rename from tests/specs/async/util/TimeUnitSpec.cfc
rename to tests/specs/async/time/TimeUnitSpec.cfc
index 8ad95e70e..d9a1950f2 100644
--- a/tests/specs/async/util/TimeUnitSpec.cfc
+++ b/tests/specs/async/time/TimeUnitSpec.cfc
@@ -9,7 +9,7 @@ component extends="tests.specs.async.BaseAsyncSpec" {
// all your suites go here.
describe( "TimeUnit", function(){
beforeEach( function( currentSpec ){
- timeUnit = new coldbox.system.async.util.TimeUnit();
+ timeUnit = new coldbox.system.async.time.TimeUnit();
} );
it( "can be created", function(){
diff --git a/tests/specs/ioc/InjectorCreationTest.cfc b/tests/specs/ioc/InjectorCreationTest.cfc
index f4b8a1722..7589c8170 100755
--- a/tests/specs/ioc/InjectorCreationTest.cfc
+++ b/tests/specs/ioc/InjectorCreationTest.cfc
@@ -229,6 +229,6 @@
function testVirtualInheritanceCreation(){
var c = injector.getInstance( "virtually-inherited-class" );
- expect( c.getData() ).toBe( "My Data" );
+ expect( c.getData() ).toBe( "Default Data" );
}
}
diff --git a/tests/specs/ioc/config/samples/InjectorCreationTestsBinder.cfc b/tests/specs/ioc/config/samples/InjectorCreationTestsBinder.cfc
index bd8e496d9..eb0ee8d14 100755
--- a/tests/specs/ioc/config/samples/InjectorCreationTestsBinder.cfc
+++ b/tests/specs/ioc/config/samples/InjectorCreationTestsBinder.cfc
@@ -1,4 +1,5 @@
-component extends = "coldbox.system.ioc.config.Binder"{
+component extends="coldbox.system.ioc.config.Binder" {
+
/**
* Configure WireBox, that's it!
*/
@@ -138,7 +139,7 @@
map( "virtually-inherited-class" )
.to( "tests.resources.ChildClass" )
- .virtualInheritance( "tests.resources.VirtualParentClass" )
- .initArg( name = "data", value = "My Data" );
+ .virtualInheritance( "tests.resources.VirtualParentClass" );
}
+
}