WhatsNew:3.0.0
What's New With ColdBox 3.0.0
Introduction
ColdBox 3.0.0 is an entirely new release and you must take care when updating from older releases as there might be come compatibility issues that can be found here: Compatibility Guide for pre 3.0.0. Below is the new configuration file XSD documentation:
Bug Fixes
You can see our defect report for ColdBox 3.0.0 at our Assembla workspace.
Application Template Updates
- New application templates
- All application generators updated also.
Major New Features
This section lists the major updates to the ColdBox 3.0.0 Platform
Programmatic Configuration
Look Ma! No more XML! That's right, you can leave your XML worries behind and configure the entire application using a simple configuration CFC. Not only that, but we added environment detection right into the new programmatic CFC. To learn more about it, please visit our configuration CFC page.
CacheBox: The Enterprise ColdFusion Cache Engine, Aggregator and API
We are opening our enterprise level cache as a standalone application but we are also enhancing it in order to create CacheBox. CacheBox is not only an enterprise level caching engine but also functions as a cache aggregator and API. You can aggregate different caching engines or types of the same engine into one single umbrella. It also gives you built in logging, events, synchronization, shutdown/startup procedures and best of all a cache agnostic API. You can then build your applications based on an abstract API and then be able to configure the caches for your applications at runtime. This gives you greater flexibility and scalability when planning and writing your applications. Our caching engine has also been revamped with new events and we now have flexible and customizable object storages. From concurrent object stores to simple JDBC replicated storages. We also opened our storage and cache provider API so you can even build your own and extend the framework. Read our CacheBox Documentation
MockBox: The ColdBox Mocking/Stubbing Framework
In one of our most ambitious releases, we are also introducing one of the newest standalone services/frameworks that are now bundled with ColdBox. MockBox is a next generation mocking/stubbing framework that is a necessity when unit testing ColdFusion components. Read our MockBox Documentation
LogBox: The Enterprise ColdFusion Logging Library
We are opening up our internal logging framework for usage not only within ColdBox applications but for ANY ColdFusion application. It becomes another addition to our standalone services/frameworks that are bundled with the ColdBox Platform. LogBox is an enterprise ColdFusion logging library designed to give you flexibility, simplicity and power when logging or tracing is needed in your applications. LogBox allows you to easily build upon its logging framework in order to meet any logging or reporting needs your applications has. LogBox allows you to create multiple destinations for your loggings and even be able to configure them or change them at runtime. LogBox was inspired by the original logging capabilities in ColdBox and in the Log4j project. Read our LogBox Documentation
- All logging for internal ColdBox classes can now be controlled via your application's LogBox configuration.
ColdBox Modules
You can now build your ColdBox applications in a more modular and segregated approach. ColdBox Modules are self-contained subsets of a ColdBox application that can be dropped in to any ColdBox application and become alive as part of the host application. They will bring re-usability and extensibility to any ColdBox application, as now you can break them down further into a collection of modules. This concept has been around in software design for a long time as it is always essential to partition a system into manageable modules or parts.
"In structured design and data-driven design, a module is a generic term used to describe a named and addressable group of program statements" by Craig Borysowich (Chief Technology Tactician)
ColdBox Modules will change the way you approach application development as you can now have a foundation architecture that can scale easily and provide you enough manageability to reduce maintenance and increase development. Such a design means that development, testing and maintenance becomes easier, faster and with a much lower cost. Welcome to a brave new world! Read our Modules Documentation
ColdBox Flash RAM
ColdBox has Flash RAM capabilities since version 2.0 (long long time ago). However, in this release we are taking a step further and enhancing the capabilities. Now every handler, plugin, interceptor, layout and view will have a flash object in their variables scope already configured for usage. The entire flash RAM capabilities are now encapsulated in a flash object that you can use in your entire ColdBox code. Not only that, we based our Flash object on an interface and now you can build your own Flash RAM implementations very easily. What this means is that you can build a Flash RAM implementation that can store the Flashed variables wherever you want.
We also added the capabilities to maintain flows or conversations between requests by using our discard() and keep() methods in our Flash objects. This will continue to expand in our 3.X releases as we build our own conversations DSL to define programmatically wizard like or complex web conversations.
For those who do not know what Flash RAM does, please read our Flash RAM Documentation. A brief introduction is that there are times where you need to store user related variables in some kind of permanent storage then relocate the user into another section of your application, be able to retrieve the data, use it and then clean it. All of these tedious operations are definitely doable by why reinvent the wheel if we can have the platform give us a tool for maintaing conversation variables across requests.
The included implementations for ColdBox 3.0.0 are:
- Session : Persists variables in session scope
- Cluster : Persists variables in cluster scope (Railo only)
- Client : Persists variables in client scope (Simple or complex, we serialize the data for you)
- ColdBoxCache : Persists variables in the ColdBox Cache
- Mock : Mocks the storage of Flashed variables. Great for unit/integration testing.
You will find all of these implementations in the following directory: coldbox.system.web.flash. Please read our Flash RAM Documentation
// handler code: function saveForm(event){ // save post flash.put("notice","Saved the form baby!"); // relocate to another event setNextEvent('user.show'); } function show(event){ // Nothing to do with flash, inflating by flash object automatically event.setView('user.show'); } // User/show.cfm template using if statements <cfif flash.exists("notice")> <div class="notice">#flash.get("notice")#</div> </cfif> // User/show.cfm using defaults <div class="notice">#flash.get(name="notice",default="")</div>
ColdBox Extensions
You can now very easily override the functionality of core plugins without touching them and you can also add more plugins to the core by using our new ColdBox Extensions. This is basically a folder in the core called coldbox/system/extensions. In this folder (as of now), you can drop in your plugins in the plugins directory. What this does is that the framework will look for core plugins in this location FIRST and then if not found look in the core plugins directory: /coldbox/system/plugins. This is extermely useful if you want to override the behavior of internal plugins or just have a location for your own global core plugins.
You can also customize the location to ANYWHERE you like by adding a simple setting to your configuration: ColdBoxExtensionsLocation. This setting is a dot notation path or cf maping to where your plugins and (*ahem*) future services (announced later), will be placed.
coldbox = {
coldboxExtensionsLocation = "shared.extensions"
};
//or
<Setting name="ColdBoxExtensionsLocation" value="shared.extensions" />
LayoutsExternalLocation
Long overdue external location setting that can act as a secondary lookup location for layouts.
ColdFusion ORM Integrations
We have created several classes that can give you greater support for ColdFusion ORM integrations via Hibernate. Read the documentation here: CF ORM Extras
- EventHandler : We added a base ORM Event Handler object that bridges your ColdBox applications to the Hibernate event model. It also allows you to inject entities with other objects using the ColdBox Autowire DSL and create more domain driven objects.
- BaseORMService : A base service layer object that can help you manage and work with entities using ORM capabilities. It can offer dynamic methods, query executions, introspections, hibernate transactions and much more.
- VirtualEntityService : We also created a virtual entity service that can help you create virtual service layers concentrated on specific entities.
This is a great way to start off your ColdFusion ORM projects. We believe these base service layers can give you a good 85% of functionality right off the bat.
ColdBox Cache Enhancements
- Lots of concurrency and load testing enhancements
- New method lookupMulti() so you can do multiple key lookups
- New method clearEventMulti() so you can clear multiple event cachings at once
ColdBox Factory Enhancements
You can now use the following new functions in the ColdBox Factory
- getRequestContext() : Get the context object
- getRequestCollection() : Get a reference to the request collection.
- getLogBox() : Get a reference to logbox
- getLogger(category) : Get a logger object
- getRootLogger() : Get a root logger object
ColdBox Proxy Enhancements
You can now use the following new functions in the ColdBox Proxy
- getRequestContext() : Get the context object
- getRequestCollection() : Get a reference to the request collection.
- getLogBox() : Get a reference to logbox
- getLogger(category) : Get a logger object
- getRootLogger() : Get a root logger object
New interception point: preProxyResults.
Plugin Enhancements
No More Inheritance
The major change to plugins is that now plugins DO NOT need to inherit from the Core ColdBox Plugin. You can now build simple CFCs and they will be decorated at runtime and adapted for usage within ColdBox. Ahh, doesn't that feel great, less coupling! Not only that, we are still backwards compatible and if you prefer to do inheritance, then go for it, we are not prejudice to your decision. You can still use ALL of the methods you are used to when building plugins, we take care of the hard stuff to make your code look nicer and easier. Also, if you declare a constructor in your plugin, ColdBox will call it for you. We also create a virtual super scope for your inheritless components so you can still provide overrides. The name of the scope is called: $super so you can talk to the base class.
** * My awesome plugin * @singleton */ component{ function init(){ setPluginName("Cool Awesome"); setPluginAuthor("Lui Majano"); return this; } }
Persistence Metadata
You can now use the singleton annotation on the cfcomponent tag to denote that this object will live for the entire application. This is the same as adding cache=true cacheTimeout=0 to the component tag:
** * @singleton true */ component{ } // OR <cfcomponent singleton=true></cfcomponent>
By pass Plugin Constructor
You can also bypass an automatic init() call on the plugin by passing a new argument to the getMyPlugin() or getPlugin() calls.
// get plugin with no constructor called, I will do it oPlugin = getMyPlugin(plugin="AwesomePlugin", init=false).init("Yeaaaa!");
Persistence Updates
You can now declare a singleton annotation on the plugin declaration and it will be treated as a singleton.
** * My awesome plugin * @singleton */ component{ function init(){ return this; } }
Retrieve Module Plugins
You can now pass in a module argument to all plugin functions to retrieve a plugin directly from a module.
// Get the Smiley plugin from the fun module smiley = getMyPlugin(name="Smiley",module="Fun");
New Object Properties
All plugins will have certain objects in the variables scope available to them for usage. The following is a table listing of all the objects you can use:
| Property | New | Description |
|---|---|---|
| logBox | Yes | The reference to the LogBox library |
| log | Yes | A pre-configured LogBox Logger object for this specific class object |
| flash | Yes | A reference to the current configured Flash Object Implementation that inherits from the AbstractFlashScope (coldbox.system.web.flash.AbstractFlashScope) |
| controller | No | The main application ColdBox controller (coldbox.system.web.Controller) |
New Methods
You now have a few extra methods when dealing with plugin development:
- getRequestContext() : Get the context object
- getRequestCollection() : Get a reference to the collection
- get/setPluginAuthor() : Get the plugin author
- get/setPluginAuthorURL() : Get the plugin author's url
New Cluster Scope Plugin
New plugin for using the cluster scope on railo enabled CFML engines.
New Validator Plugin
A plugin that helps you do any kind of data validation, check its API out: http://www.coldbox.org/api (coldbox.system.plugins.Validator)
New ORMService Plugin
We added a new plugin that will act as a generic ORM service layer for your applications. This generic service layer will give you a nice way for you to build application using a simple service layer already pre-coded. This service layer is based on our BaseORMService class.
Utilities Plugin Refactored
The Utilities plugin has been refactored into several new plugins:
- DateUtils : Helps all your date time necessities
- FileUtils : Helps all your file necessities
- JVMutils : Be cool and talk to the JVM
- Utilities : Generic utility methods
New HTMLHelper Plugin
This cool new little plugin will help you out when dealing with HTML. It can help you keep track of js or css assets and make sure they get loaded only once, render strict html css or javascript, render lists from arrays or queries, render tables from arrays or queries, create cool dynamic meta tags, create doctype headers and so much more. Please read our HTMLHelper Documenation for more information and awesomeness!
New MailService Plugin
You can now be cool and send emails via our very own MailService plugin. Use it in script and in any CFML engine. Not only that but this service can actually do token replacements for you at the time of sending emails. So what is this? Well, once you tell the mail service to give you a new mail object, you can set attachments on it, headers, etc but you can also pass in a structure of tokens. Once the email is about to be sent, the MailService will do token replacements on the body of the email agains these tokens.
- Each token is delimited by the following @token@
// Get new mail object mail = getPlugin("MailService").newMail().config(from="info@coldbox.org", to="awesome@coldbox.org", type="html", subject="Mail For You Captain Awesome"); // create some tokens tokens = {name="Luis Majano",time=dateformat(now(),"full")}; mail.setBodyTokens(tokens); // Set some body text mail.setBody("<h1>Hello @name@, how are you today?</h1> <p>Today is the <b>@time@</b>.</p> <br/><br/><a href=""http://www.coldbox.org"">ColdBox Rules!</a>"); // Send the mail results = getPlugin("MailService").send(mail);
MessageBox Updates
Some new convenience methods:
- info() : To render info message directly
- warning() : To render a warning
- error() : To render an error
If you want to style your own messagebox you will need to create a setting called: messagebox_style_override and set it to true. Then make sure the css for the messagebox exists.
settings = {
messagebox_style_override = true
};
You can now also render messages a-la-carte or on-demand by using the renderMessage method:
#getPlugin("MessageBox").renderMessage("info","Info Message")# #getPlugin("MessageBox").renderMessage("warning",flash.get('notice'))#
AntiSamy XSS Plugin Updated
The AntiSamy XSS plugin has been updated from the latest AntiSamy release project: http://www.owasp.org/index.php/Category:OWASP_AntiSamy_Project
Yes, ColdBox has XSS Cleanup built in since version 2.0
Renderer Plugin
- All rendering methods updated to support module renderings.
- renderView() now has some new optional arguments:
- cacheSuffix : A suffix to add to the caching keys for the view
- module : Will render the view in the specified module
- renderLayout() can now be used a-la-carte and with some cool optional arguments
- layout : The layout to render
- view : The view argument to pass into the layout
- module : Will render the layout in the specified module
View Folder Helper
We also introduce view folder helpers. We already have view helpers by conventions, basically if you have a view called home.cfm and you create alongside it a file called homeHelper.cfm that helper's UDFs will be injected at runtime into the home.cfm and you can use those UDFs. For 3.0.0 we expanded on this (thanks to an awesome student at one of our trainings) and we introduce a view folder helper. What this means is that you can create a convention based on the folder name and all the views within that folder will get to use the helper UDFs. So if I have the following layout:
/views /users index.cfm editor.cfm permissions.cfm listings.cfm usersHelper.cfm *
You can see that all the views are in the users folder, and I created a usersHelper.cfm. Basically: {name of folder}Helper.cfm. The Renderer now sees a folder helper and will inject it for all the renderings within this folder ONLY.
Query Helper
- Addition of getCSV() to convert a query to a CSV string
- Sorting can now also be case-insensitive
Feed Generator
- Additional support formats like iTunes/Atom and so much More
- Fully documented with new sample applications
Feed Reader
- Added multiple new XML supports
- Added more types of generated content from reading feeds
- Fully documented with new sample applications
i18n & Resource Bundle
Both the i18n and ResourceBundle plugins have been completely reworked and reconstructed for this release. They are now faster, leaner and meaner! Their performance is substanially faster and more stable under race conditions as lots of thread conditions have been eliminated. Below are some of the major enhancements apart from performance and reworking.
- If the translation requested is not found in the localized resource bundle, the mechanisms will look for it in the default language bundles
- getResource(resource,[default],[locale]) is now the new signature for this method, which enables you to pass in a default value to return if the resource is not located in the user's locale language. You can also pass in an explicit locale and the resource will be retrieved from that locale instead of detecting the user's set locale.
- getRBString(rbFile,rbKey,rbLocale,default) is also updated to include locale and default arguments.
- When resources are retrieved using the getResource() method and it does not exist, the plugin will now also show you which key was not found.
- getfwLocale() and setfwLocale() are 3 times faster than before.
// show a resource and if it does not exist, return a value #getResource('name.button','Click')# // Show the resource from the spanish locale, I am building a spanish email ala carte <div> #getResource(resource="welcome",locale="es")# Francisco, Gracias! </div>
JavaLoader Enhancements
- You can now call a method on the plugin called getLoadedURLs() so it can give you an array of all the paths that have been loaded by the JavaLoader library.
- Added the ability to load more jar's or class files to an already running JavaLoader instance by using the method appendPaths(dirPath,filter)
- You can have a custom setting called javaloader_libpath in your configuration file that can be a single location or an array of locations the java loader library will look for jar files to load.
Interceptor Enhancements
- The methods setNextEvent() and setNextRoute() now execute a postProcess interception point. It can also be deactivated if need be.
- When you declare interceptors you can add a name attribute which uniquely identifies the declared interceptor. This allows you to declare more than once instance of an interceptor in different configurations. If no name is defined, then the name will be the name of the component.
// Declare interceptor interceptors = [ {name="AwesomeInterceptor", class="coldbox.system.interceptors.Awesome"}, {name="MoreAwesomeInterceptor", class="coldbox.system.interceptors.Awesome", properties={ awesomeCheck=true, configLoading=false } } ]; <-- OOr XML--> <Interceptors> <Interceptor name="AwesomeInterceptor" class="coldbox.system.interceptors.Awesome" /> <Interceptor name="AwesomeInterceptor" class="coldbox.system.interceptors.Awesome"> <Property name="awesomeCheck">true</Property> <Property name="configLoading">false</Property> </Interceptor> </Interceptors>
No More Inheritance
The major change to interceptors is that now they DO NOT need to inherit from the Core ColdBox Interceptor class if you like. You can now build simple CFCs and they will be decorated at runtime and adapted for usage within ColdBox. Ahh, doesn't that feel great, less coupling! Not only that, we are still backwards compatible and if you prefer to do inheritance, then go for it, we are not prejudice to your decision. You can still use ALL of the methods you are used to when building interceptors, we take care of the hard stuff to make your code look nicer and easier. We also create a virtual super scope for your inheritless components so you can still provide overrides. The name of the scope is called: $super so you can talk to the base class.
** * My awesome Interceptor */ component{ //Autowire Dependencies property name="userService" inject; function configure(){ // configure my interceptor } function preProcess(event,interceptData){ } }
New Interception Points
| Interception Point | Description |
|---|---|
| preLayout | Occurs before any rendering is done or determined. Remember that the preRender point has the entire rendered content passed into it already. So this happens before rendering of content |
| preViewRender | Occurs before ANY view is executed. Keys received in the interception: renderedView,view,cache,cacheTimeout,cacheLastAccessTimeout,cacheSuffix,module |
| postViewRender | Occurs after the view has been executed and is awaiting to be rendered. Receives all the view rendering arguments and the view content that will be rendered. Keys: renderedView,view,cache,cacheTimeout,cacheLastAccessTimeout,cacheSuffix,module |
| preProxyResults | Occurs before the results of a proxy call are sent back to the external caller. This can be used for marshalling or transformation of data, logging, etc. Keys received are: proxyResults |
| afterModelCreation | Occurs after ANY model object gets created and can be used to post-process objects after creation and dependency injections. |
| onReinit | Occurs right before the application will be reinitialized. |
| onApplicationEnd | Fires whenever the ColdFusion application stops. A great place to shutdown services such as EHCache, etc. |
| beforeDebuggerPanel | Fires right before the debugger panels get rendered in debug mode |
| afterDebuggerPanel | Fires right after the last debugger panel gets rendered in debug mode. |
Extending The Debugger
Thanks to two new interception points: beforeDebuggerPanel,afterDebuggerPanel, you can easily build interceptors that can act as debugger panels in your ColdBox applications. Time to start getting creative folks!
New Object Properties
All interceptors will have certain objects in the variables scope available to them for usage. The following is a table listing of all the objects you can use:
| Property | New | Description |
|---|---|---|
| logBox | Yes | The reference to the LogBox library |
| log | Yes | A pre-configured LogBox Logger object for this specific class object |
| flash | Yes | A reference to the current configured Flash Object Implementation that inherits from the AbstractFlashScope (coldbox.system.web.flash.AbstractFlashScope) |
| controller | No | The main application ColdBox controller (coldbox.system.web.Controller) |
eventPattern annotation
You can now add a new metadata argument (annotation) called eventPattern to any interceptor method in order to tell the framework that it should execute that interception point ONLY if the incoming event matches that regular expression. Let's say that you have an interceptor with a preProcess method and you would like to provide security with it, but only to events that are in the admin package. Then I can do this:
/** * @eventPattern ^admin */ function preProcess(event,interceptData){ } //OR <cffunction name="preProcess" output="false" eventPattern="^admin"> </cffunction>
The eventPattern annotation is a regular expression that MUST match the incoming event variable. If it does, then the interception point fires, else it is ignored.
New Interceptor: ReactorLoader
The reactor extras have been expanded and a new interceptor has been born called: !ReactorLoader. This nice interceptor will configure your application to use Reactor and cache it in the ColdBox cache. Easily configure your app for Reactor ORM
Interceptor Properties :
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| dsnAlias | string | true | N/A | The datasource alias name to use, as defined in your datasources section of your config. Make sure you define the dbtype also |
| pathToConfigXML | string | true | N/A | The path of the Reactor config file |
| project | string | true | N/A | The name of the Reactor project |
| mapping | string | true | N/A | The relative path or mapping to the directory where reactor will write generated files |
| mode | string | true | N/A | The reactor mode: always,development,production |
| ReactorCacheKey | string | false | Reactor | The key used to store Reactor as a singleton in the coldbox cache. (case sensitive) |
| ReactorConfigClassPath | string | false | reactor.config.config | The class path override of the reactor configuration object |
| ReactorFactoryClassPath | string | false | reactor.reactorFactory | The class path override of the reactor factory object |
So just remember to add the datasource element with a valid dbType as Reactor needs it; Options are:
- mssql - Microsoft SQL Server 2000 and 2005.
- mysql - MySQL 5
- mysql4 - MySQL 4
- postgresql - PostgreSQL 8
- db2 - IBM DB2
- oracle - Oracle 9i and 10g
- oraclerdb - Oracle RDB (this is not officially supported)
Sample:
<Datasources> <Datasource alias="blogDSN" name="simpleblog" dbtype="mssql" /> </Datasources> <Interceptors> <Interceptor class="coldbox.system.orm.reactor.ReactorLoader"> <Property name="dsnAlias">blogDSN</Property> <Property name="pathToConfigXML">config/reactor.xml.cfm</Property> <Property name="project">MyBlog</Property> <Property name="mapping">${AppMapping}/model/reactor</Property> <Property name="mode">development</Property> </Interceptor> </Interceptors>
This declaration will create the following objects in the ColdBox cache:
| Cache Key | Object |
|---|---|
| Reactor | The reactor factory object |
So do you still think there is more? Well, there is no more, that's it, a few lines of code and you are cooking with Reactor.
Autowire Interceptor Updates
The autowire interceptor has been udpated to allow for the configuration of dependency injection of ORM entities. This feature ONLY works if you are using our new ORM Event Handler to ColdBox bridge. If enabled you can configure the following settings:
| Property | Type | Default | Description |
|---|---|---|---|
| entityInjection | Boolean | false | Enable the entity injection events |
| entityInclude | List | [empty] | A list of entity names to include in the injections ONLY! |
| entityExclude | List | [empty] | A list of entity names to exclude from dependency injection! |
Security Interceptor Updates
- The property useRoutes has now been deprecated as it is automatically calculated.
- You can now add a new element called useSSL to the XML or query rules so the interceptor will use SSL or not to redirect or secure the rule.
<rule> <whitelist>user\.login,user\.logout,^main.*</whitelist> <securelist>^user\..*, ^admin</securelist> <roles>admin</roles> <permissions></permissions> <redirect>user.login</redirect> <useSSL>false</useSSL> </rule>
- You can now also add ANY element to a rule and it will be read and passed to the validators for usage:
<rule> <whitelist>user\.login,user\.logout,^main.*</whitelist> <securelist>^user\..*, ^admin</securelist> <roles>admin</roles> <permissions></permissions> <redirect>user.login</redirect> <useSSL>false</useSSL> <--C<ustom Elements--> <Filters>auth,cookie,ip</Filters> <CustomChecks>true</CustomChecks> </rule>
Deploy Interceptor Updates
- New property: deployCommandModel that points to a model alias (object) that will be used as the deployment command object. This is the object that gets fired when a new deployment gets detected.
SES Interceptor Updates
- addCourse() is now deprecated and replaced by addRoute()
- New view dispatcher : You can now declare views to be dispatched with no need of running events in SES mode. This is accomplished via our new view argument to the addRoute() method. You can even use the viewNoLayout to tell the route to skip a layout, else the argument is always false.
// view dispatch, no event execution addRoute(pattern='/AboutUs',view='about',viewNolayout=true);
- Ability to turn on/off convention name value pairs on a per route basis by adding a new argument to the addRoute() method: valuePairTranslation (boolean), which defaults to true.
- You can now declare a / route pattern that indicates the default event instead of defining one in the configuration file.
- Performance updates are now even visible due to the evaluation of regex patterns at application startup instead of at runtime and for each route permutation.
- New method to allow for routes to be reloaded in every request. Great for development or on the fly routes: setAutoReload(true)
- You can now build alpha only placeholders by using -alpha on them.
addRoute(pattern="/:name-alpha",handler="show");
- Ability to do REST URLs based on HTTP Methods. You can do this by passing a JSON string or structure to the action argument
// Easily split actions on a handler according to HTTP methods detected. addRoute(pattern="/api/user/:username", handler="rest.User", action={ GET = "show", DELETE = "remove", POST = "create", PUT = "update" });
- You can now make the placeholders adhere to your own regular expressions by using the constraints argument which is a JSON structure or CF structure of name value pairs that match the placeholder name.
// route with custom constraints addRoute(pattern="/api/:format/", handler="api", action="execute", contraints={ format = "(xml|json)" });
Important : The value of the constraint MUST be enclosed in parenthesis ()
- New ses regex dsl for placeholders: /regex:(full regex here)/
This gives the user the ability to use ANY regular expression for any placeholder or for the entire URL string by just writing the namespace: regex:() and then placing the regular expression within the parenthesis. This is similar to the constraints functionality but targeted directly to a placeholder reference within the pattern. This is more readable and directed but it does not set the regular expression to a variable. It just identifies and matches.
// route with custom constraints addRoute(pattern="/api/regex:(^(xml|json))/", handler="api", action="execute");
Event Handler Enhancements
No More Inheritance
The major change to handlers is that now they DO NOT need to inherit from the Core ColdBox EventHandler if you like. You can now build simple CFCs and they will be decorated at runtime and adapted for usage within ColdBox. Ahh, doesn't that feel great, less coupling! Not only that, we are still backwards compatible and if you prefer to do inheritance, then go for it, we are not prejudice to your decision. You can still use ALL of the methods you are used to when building event handlers, we take care of the hard stuff to make your code look nicer and easier. Also, if you declare a constructor in your handler, ColdBox will call it for you. We also create a virtual super scope for your inheritless components so you can still provide overrides. The name of the scope is called: $super so you can talk to the base class.
** * My awesome Handler */ component{ //Autowire Dependencies property name="userService" inject; function init(){ // do some init stuff return this; } }
Persistence Metadata
You can now use the singleton annotation on the cfcomponent tag to denote that this object will live for the entire application. This is the same as adding cache=true cacheTimeout=0 to the component tag:
** * @singleton true */ component{ } // OR <cfcomponent singleton=true></cfcomponent>
New Object Properties
All handlers will have certain objects in the variables scope available to them for usage. The following is a table listing of all the objects you can use:
| Property | New | Description |
|---|---|---|
| logBox | Yes | The reference to the LogBox library |
| log | Yes | A pre-configured LogBox Logger object for this specific class object |
| flash | Yes | A reference to the current configured Flash Object Implementation that inherits from the AbstractFlashScope (coldbox.system.web.flash.AbstractFlashScope) |
| controller | No | The main application ColdBox controller (coldbox.system.web.Controller) |
Pre/Post Interceptors Updated
These local interceptors now take in the current executing action they are intercepting as a new argument called: action
<--- preHandler ---> <cffunction name="preHandler" returntype="void" output="false" hint="Executes before any event in this handler"> <cfargument name="event" required="true"> <cfargument name="action" hint="The intercepted action"/> <cfscript> var rc = event.getCollection(); </cfscript> </cffunction> <--- postHandler ---> <cffunction name="postHandler" returntype="void" output="false" hint="Executes after any event in this handler"> <cfargument name="event" required="true"> <cfargument name="action" hint="The intercepted action"/> <cfscript> var rc = event.getCollection(); </cfscript> </cffunction>
New implicit handler method: onError()
You can now create another implicit method on your event handlers with the following signature:
function onError(event,faultAction,exception){
}
This method will be executed if there is ANY exception executing the curent event handler. This can give you very nice controlled exceptions at a handler or base handler level.
// Implicit Error Handler function onError(event,action,exception){ var validator = getInterceptor("APIValidator"); var append = structnew(); // Log Error append.exception = arguments.exception; logger.fatal("Error executing resource: #event.getValue("_route")#. Error: #arguments.exception.message#",validator.getLogJSON(append)); // Send error to screen validator.renderError(message="#arguments.exception.message# #arguments.exception.detail#", code="500", format=arguments.event.getValue("format"), event=arguments.event); }
Handler Action HTTP Security
All event handlers can now declare a public variable called allowedMethods (struct) that contains a map of allowed methods and their appropriate HTTP methods. This has been done to provide a nice way to secure RESTful API's built on ColdBox. You can now control what HTTP method operations can be done on specific handler actions. Let's say we want to protect our actions on our handler, delete(),add() and index() with different http methods.
| Action | Allowed Methods |
|---|---|
| delete | delete |
| add | get,post |
| index | get |
//public property to allow method executions this.allowedMethods = { delete='delete', add='get,post', index='get' };
New Implicit Handler: MissingTemplateHandler
You can now declare a coldbox setting called: missingTemplateHandler that points to an event. This event will be fired whenever the framework detects a missing template call in your application, that is for ANY cfm template. The request context will have the missing template value in a variable called missingTemplate.
// Declare the missing template handler in your Coldbox.cfc or your coldbox.xml.cfm coldbox = { missingTemplateHandler = "main.missingTemplate" }; // XML <Settings> <Setting name="missingTemplateHandler" value="main.missingTemplate" /> </Settings> // The handler event function missingTemplate(event){ var rc = event.getCollection(); rc.page = pageService.findByName( rc.missingTemplate ); if( rc.page.isValid() ){ // Render the page event.setView('page.show'); } else{ // invalid page setNextEvent('page.invalid'); } }
New Implicit Handler: applicationEndHandler
You can now declare a coldbox setting called: applicationEndHandler that points to an event. This event will be fired whenever the ColdFusion Application expires or stops.
// Declare the missing template handler in your Coldbox.cfc or your coldbox.xml.cfm coldbox = { applicationEndHandler = "main.onAppEnd" }; // XML <Settings> <Setting name="applicationEndHandler" value="main.onAppEnd" /> </Settings> // The handler event function onAppEnd(event){ var rc = event.getCollection(); //maybe shutdown things getModel("EHCache").shutdown(); }
Model Integration Enhancements (WireBox)
- The model integration framework in ColdBox is now called WireBox and it will be standalone for use outside of ColdBox.
- You can now use the keyword singleton in the cfcomponent tag to simulate a singleton instance.
- New interception point: afterModelCreation that can be used to post-process objects after creation and dependency injections.
- Factory now respects defaults on constructor arguments
- Factory now ignores objects that cannot be located or found
- Debug mode is now enhanced to reflect more data via LogBox
- getModel() has new arguments
- executeInit=[true] : Whether to execute the constructor or not.
- dsl : You can optionally pass in a DSL string instead of a name to create a domain object
- A backup namespace has been added to package scanning of path /. This means that WireBox can now by default locate ANY object in the server just like createObject() does but with autowiring goodness.
New/Updated Settings
The new/updated settings you can use when configuring the ColdBox Model Integration:
- DefinitionFile : By default points to config/ModelMappings.cfm. You can use this setting to tell WireBox where to find the configuration file.
- ExternalLocation : It is now a list of invocation paths that the WireBox bean factory will search CFCs for. Please remember that order of declaration is important as that will be the order of search.
// Programmatic aproach //Model Integration models = { objectCaching = true, definitionFile = "config/modelMappings.cfm", externalLocation = "coldbox.testing.testmodel,shared,transfer", SetterInjection = false, DICompleteUDF = "onDIComplete", StopRecursion = "" }; // XML Approach <Models> <DefinitionFile>config/ModelMappings.cfm</DefinitionFile> <SetterInjection>false</SetterInjection> <ObjectCaching>true</ObjectCaching> <ExternalLocation></ExternalLocation> <DICompleteUDF>onDIComplete</DICompleteUDF> <StopRecursion></StopRecursion> </Models>
Dependency DSL Metadata
The dependency DSL metadata marker has been standardized to the keyword inject. You can now use this metadata annotation on
- cfproperty tags
- constructor arguments
- setter methods
// some examples property name="userService" inject="model"; //same as inject by default references a model object property name="userService" inject; // seter method /** * @inject Model */ function setUserService(userService){ variables.userService = arguments.userService; } // Constructor arguments <cffunction name="init" output="false" returntype="any"> <cfargument name="userService" type="any" inject="model"> </cffunction>
Updated Dependency Injection DSL
More updates will be shown once the WireBox Documentation is finalized. Below are some new autowire DSL types:
| Type | Description | ||
|---|---|---|---|
| cachebox | Get a reference to the application's CacheBox instance | ||
| cachebox:{name} | Get a reference to a named cache inside of CacheBox | ||
| cachebox:{name}:{objectKey} | Get an object from the named cache inside of CacheBox according to the objectKey | ||
| coldbox:moduleService | Get a reference to the ColdBox Module Service | ||
| coldbox:interceptor:{name} | Get a reference of a named interceptor | ||
| coldbox:fwConfigBean | Get a configuration bean object with ColdBox settings instead of Application settings | ||
| coldbox:fwSetting:{setting} | Get a setting from the ColdBox settings instead of the Application settings | ||
| javaLoader:{class} | Create an object from the JavaLoader plugin and its set of loaded java libraries | ||
| entityService | Inject a BaseORMService object for usage as a generic service layer | ||
| entityService:{entity} | Inject a VirtualEntityService object for usage as a service layer based off the name of the entity passed in. | logbox | Get a reference to the application's LogBox instance |
| logbox:root | Get a reference to the root logger | ||
| logbox:logger:{category name} | Get a reference to a named logger by its category name |
// some examples property name="logbox" inject="logbox"; property name="rootLogger" inject="logbox:root"; property name="logger" inject="logbox:logger:model.com.UserService"; property name="moduleService" inject="coldbox:moduleService"; property name="producer" inject="coldbox:interceptor:MessageProducer"; property name="configBean" inject="coldbox:fwConfigBean"; property name="producer" inject="interceptor:MessageProducer"; property name="appPath" inject="coldbox:fwSetting:ApplicationPath"; // JavaLoader goodness property name="binaryHeap" inject="javaLoader:org.apache.commons.collections.BinaryHeap"; property name="email" inject="javaLoader:org.apache.commons.mail.SimpleEmail"; // Generic ORM service layer property name="genericService" inject="entityService"; // Virtual service layer based on the User entity property name="userService" inject="entityService:User";
Request Context Enhancements
These are the updates on the request context object:
- buildLink(linkTo,translate,ssl,baseURL,queryString) : now supports a query string argument for building ses or non-ses query strings and also an ssl argument.
- The setLayout(layout) method can now accept the layout alias also instead of the file name. This is only valid if you have declared your layouts implicitly in your configuration file.
- The setView() now takes in two extra optional arguments:
- layout : The layout to render the view in
- cacheSuffix : You can now pass in a suffix to that will be added to the cache keys (maybe the host your are on?)
New Methods
- getHTTPMethod() : New method gives you the currently executing http method the request is coming from.
- noExecution() : New method that disables the execution of the main event.
- getCurrentRoutedURL() : Gives you the currently routed URL via SES
- getCurrentModule() : Gives you the name of the curently executing module (if any)
- getCurrentRoute() : Gives you the current matched SES URL Mapping route
- getHTTPHeader(key,[default]) : Nice litte tool to get you HTTP headers with default values
- setHTTPHeader([statusCode],[statusText],[name],[value],[charset]) : Nice little tool to set your HTTP headers, even mock them
Examples
// build link with query string <a href="#event.buildLink(linkTo='users.list',queryString='userID=234&page=2')#">Click</a> <a href="#event.buildLink(linkTo='users.list',queryString='userID=234&page=2',ssl=true)#">Click</a> // Get HTTP method if( event.getHTTPMethod() eq "GET" ){ //do something } // Following in the coldbox.cfc config layouts = [ {name="Awesome", file="Layout.Awesome.cfm"}, {name="login", file="layout.login.cfm", folders="security"} ]; // Then in the handler event.setLayout('Awesome') // Same as event.setLayout('Layout.Awesome');
event.noExecution()
This method is a very cool method to disable the main execution of the incoming event (if any). Why would you want to do this? Well, you can build interceptors that handle the requests instead of main events. This simulates building servlets that react to interceptors instead of events. The most common usage of this method is on handlers or interceptors that occur BEFORE the execution of the main event.
Example: I will build an interceptor that listens on preProcess method and stops execution to render back some data.
function preProcess(event,interceptData){ // Do some work, we intercepted the request // Render data event.renderData(data=data,type="xml"); // Stop main event execution, there will be none event.stopExecution(); }
event.renderData() updated
Our coolest methd of all gets even cooler!
- You can now manipulate some of the json and xml produced
- We built our very own XML marshaller which is lightweight and can even marshall objects
- You can now also send status codes and text to the header in one single shot
Below is the new signature:
public void renderData(string type, any data, string contentType, [string encoding], [numeric statusCode], [string statusText], [string jsonCase], [string jsonQueryFormat], [boolean jsonAsText], [string xmlColumnList], [boolean xmlUseCDATA], [string xmlListDelimiter], [string xmlRootName])
Parameters
- type - The type of data to render. Valid types are JSON, JSONT, XML, WDDX, PLAIN/HTML, TEXT. The deafult is HTML or PLAIN. If an invalid type is sent in, this method will throw an error
- data - The data you would like to marshall and return by the framework
- contentType - The content type of the data. This will be used in the cfcontent tag: text/html, text/plain, text/xml, text/json, etc. The default value is text/html. However, if you choose JSON this method will choose application/json, if you choose WDDX or XML this method will choose text/xml for you. The default encoding is utf-8
- encoding - The default character encoding to use
- statusCode - The HTTP status code to send to the browser. Defaults to 200
- statusText - Explains the HTTP status code sent to the browser.
- jsonCase - JSON Only: Whether to use lower or upper case translations in the JSON transformation. Lower is default
- jsonQueryFormat - JSON Only: query or array
- jsonAsText - If set to false, defaults content mime-type to application/json, else will change encoding to plain/text
- xmlColumnList - XML Only: Choose which columns to inspect, by default it uses all the columns in the query, if using a query
- xmlUseCDATA - XML Only: Use CDATA content for ALL values. The default is false
- xmlListDelimiter - XML Only: The delimiter in the list. Comma by default
- xmlRootName - XML Only: The name of the initial root element of the XML packet
event.renderData(data="<h1>Page not found</h1>",statusCode="404",statusMessage="Page Not Found"); event.renderData(type="xml", data=query, xmlRootName="Users",xmlUseCDATA=true); event.renderData(type="json",data=query,jsonCase="UPPER");
ColdBox BootStrapper Enhancements
- Performance and loading updates
- Ability to change the locking timeouts: setLockTimeout()
- Ability to set the key to use in Application scope: setColdbox_app_key() or use the coldbox_app_key variables in Application.cfc
- Ability to set the appMapping: setColdbox_App_Mapping() or use the coldbox_app_mapping variables in Application.cfc
ColdBox Main Controller Enhancements
The main coldbox controller which is used by the Application.cfc/cfm now has exposed methods for the locking timeouts.
- get/setLockTimeout()
You can now also choose the name of the key used to store your running ColdBox application. By default it still uses the key: cbController, but now you can choose this by using the following methods:
- setCOLDBOX_APP_KEY() or
- Adding a variable called COLDBOX_APP_KEY in your Application.cfc declaration.
- Adding a variable called COLDBOX_APP_KEY in your template that includes the coldbox.cfm bootstrapper
The setNextEvent() has been updated to support multiple arguments:
Unit/Integration Testing Enhancements
- cfcUnit support has now been deprecated in favor of MXUnit.
- The execute() method calls now follow suite of the full request lifecycle:
- Execute ApplicationStartHandler
- Execute preProcess interceptors
- Execute RequestStartHandler
- Execute event in testing
- Execute RequestEndHandler
- Execute postProcess interceptors
- New public property for telling the testing framework to load a mock coldbox application or not when testing: loadColdbox (boolean). This defaults to true. So if you want to use the BaseTestCase for unit testing model objects, you can do so very easily, by just saying this.loadColdbox = false.
function setup(){ this.loadColdBox = false; super.setup(); }
- New annotations on the test case cfcomponent tag in order to tell it what application to test:
| Annotation | Type | Required | Default | Description |
|---|---|---|---|---|
| appMapping | string | false | / | The application mapping of the ColdBox application to test. By defaults it maps to the root |
| configMapping | string | false | {appMapping}/config/Coldbox.cfc or xml | The configuration file to load for this test. If not defined, then by default it looks for the CFC configuration file first and if not found, it tests for the coldbox.xml.cfm second. It searches in the convention of the config folder. |
| loadColdBox | Boolean | false | true | If you call super.init() on the test case, this flag tells the base test case to load up the virtual testing application or not. |
So now you can declare an integration test so much cleaner and nicer:
/** * Integration test for my Users.cfc handler * setup() is called implicitly from the inherited BaseTestCase, you can also override it. * @appMapping /my-nested-app */ component extends="coldbox.system.testing.BaseTestCase"{ function testIndex(){ event = execute('Users.index'); // Assert here } function testSave(){ // setup some data for the handler, FORM or URL form.firstname = "luis"; form.lastname = "majano"; // execute integration test event = execute('Users.save'); //assert here } }
New Methods
There are some new methods to help you in your unit testing:
| Method | Description |
|---|---|
| querySim() | enables you to simulate ANY query (Part of MockBox) |
| getMockBox() | Get a reference to MockBox |
| getMockRequestContext(clearMethods) | Builds a new request context object that you can use for mocking purposes |
| getMockModel(name,clearMethods) | Gives you back a mock model object |
| reset() | Clears the application scope of any leftover coldbox bits |
| getFlashScope() | Gives you the current loaded flash scope object |
New Targeted Plugin Testing
You can now test plugins directly with no need of doing integration testing. This way you can unit test plugins directly very very easily. All you need to do is the following:
- Create a test class that inherits from coldbox.system.testing.BasePluginTest
- Create a component annotation called plugin that equals the full path of the plugin to target for testing
This testing support class will create your plugin, and decorate with mocking capabilities via MockBox and mock all the necessary companion objects around plugins. The following are the objects that are placed in the variables scope for you to use:
- plugin : The target plugin to test
- mockController : A mock ColdBox controller in use by the target plugin
- mockRequestService : A mock request service object
- mockLogger : A mock logger class in use by the target plugin
- mockLogBox : A mock LogBox class in use by the target plugin
- mockFlash : A mock flash scope in use by the target plugin
All of the mock objects are essentially the dependencies of plugin objects. You have complete control over them as they are already mocked for you. We actually use this approach to test all shipped ColdBox plugins.
Basic Setup
/** * @plugin myApp.plugins.CoolPlugin */ component extends="coldbox.system.testing.BasePluginTest"{ // Just create test methods, no need to use the setup() method unless you want to: function setup(){ super.setup(); // test custom constructor plugin.init(); } }
Real Sample
<cfcomponent extends="coldbox.system.testing.BasePluginTest" plugin="coldbox.system.plugins.HTMLHelper"> <cfscript> function testaddAssetJS(){ var mockEvent = getMockRequestContext(); mockRequestService.$("getContext", mockEvent); // mock the plugin's htmlhead method plugin.$("$htmlhead"); // Call method to test plugin.addAsset('test.js,luis.js'); debug( plugin.$callLog().$htmlhead); // test duplicate call assertEquals('<script src="test.js" type="text/javascript"></script><script src="luis.js" type="text/javascript"></script>' , plugin.$callLog().$htmlhead[1][1] ); plugin.addAsset('test.js'); assertEquals(1, arrayLen(plugin.$callLog().$htmlHead) ); } function testTableORM(){ data = entityLoad("User"); str = plugin.table(data=data,includes="firstName"); debug(str); assertEquals('<table><thead><tr><th>firstName</th></tr></thead><tbody><tr><td>Joe</td></tr><tr><td>Luis</td></tr></tbody></table>',str); } </cfscript> </cfcomponent>
New Targeted Handler Testing
You can now test handlers directly with no need of doing integration testing. This way you can unit test handlers directly very very easily. All you need to do is the following:
- Create a test class that inherits from coldbox.system.testing.BaseHandlerTest
- Create a component annotation called handler that equals the full path of the handler CFC to target for unit testing
- Create an optional annotation called UDFLibraryFile that will be the path of the UDF library file that is declared in your ColdBox configuration or any other UDF file you load dynamically in your handlers.
This testing support class will create your handler, and decorate with mocking capabilities via MockBox and mock all the necessary companion objects around handlers. The following are the objects that are placed in the variables scope for you to use:
- handler : The target handler to test
- mockController : A mock ColdBox controller in use by the target handler
- mockRequestService : A mock request service object
- mockLogger : A mock logger class in use by the target handler
- mockLogBox : A mock LogBox class in use by the target handler
- mockFlash : A mock flash scope in use by the target handler
All of the mock objects are essentially the dependencies of handler objects. You have complete control over them as they are already mocked for you.
Basic Setup
/** * @handler myApp.handler.User * @UDFLibraryFile /myApp/includes/helpers/AppHelper.cfm */ component extends="coldbox.system.testing.BaseHandlerTest"{ // Just create test methods, no need to use the setup() method unless you want to: function setup(){ super.setup(); mockUserService = getMockBox().createEmptyMock("myapp.model.user.UserService"); // wire in my mock dependencies as this handler already has mocking capabilities handler.$property("userService","variables",mockUserService); } }
New Targeted Interceptor Testing
You can now test interceptors directly with no need of doing integration testing. This way you can unit test interceptors directly very very easily. All you need to do is the following:
- Create a test class that inherits from coldbox.system.testing.BaseInterceptorTest
- Create a component annotation called interceptor that equals the full path of the handler CFC to target for unit testing
- Create an optional strcuture variable in the variables scope called configProperties in the setup() method before your super.setup() method, if you would like to test the interceptor with your configuration properties structure.
This testing support class will create your interceptor, and decorate with mocking capabilities via MockBox and mock all the necessary companion objects around interceptors. The following are the objects that are placed in the variables scope for you to use:
- interceptor : The target interceptor to test
- mockController : A mock ColdBox controller in use by the target interceptor
- mockRequestService : A mock request service object
- mockLogger : A mock logger class in use by the target interceptor
- mockLogBox : A mock LogBox class in use by the target interceptor
- mockFlash : A mock flash scope in use by the target interceptor
All of the mock objects are essentially the dependencies of interceptor objects. You have complete control over them as they are already mocked for you.
Basic Setup
/** * @interceptor myApp.interceptors.Security */ component extends="coldbox.system.testing.BaseInterceptorTest"{ // Just create test methods, no need to use the setup() method unless you want to: function setup(){ configProperties = { roles = "admin,user,moderator", security = "active" }; super.setup(); mockSecurityService = getMockBox().createEmptyMock("myapp.model.SecurityService"); // wire in my mock dependencies as this interceptor already has mocking capabilities interceptor.$property("securityService","variables",mockSecurityService); // we are now ready to test this interceptor } }
Real Example
<cfcomponent extends="coldbox.system.testing.BaseInterceptorTest" interceptor="coldbox.system.interceptors.Deploy"> <cfscript> function setup(){ super.setup(); // mocks mockController.$("getAppRootPath",expandPath('/coldbox/testharness')); interceptor.setProperty("tagFile","config/.deploy_tag"); interceptor.$("locateFilePath","config/.deploy_tag").$("setSetting"); } function testConfigure(){ interceptor.configure(); } function testAfterAspectsLoad(){ mockLogger.$("info"); interceptor.afterAspectsLoad(getMockRequestContext()); } function testPostProcess(){ //mocks mockController.$("getColdboxInitiated",true).$("setColdboxInitiated").$("setAspectsInitiated"); testDate = now(); mockLogger.$("info").$("error"); interceptor.$property("tagFilePath","instance",'config/.deployTag'); interceptor.$property("deployCommandObject","instance",''); // Test no setting interceptor.$("settingExists",false).$("configure"); interceptor.postProcess(getMockRequestContext()); assertEquals( 1, arrayLen(interceptor.$callLog().configure) ); // Test setting exists but same date interceptor.$("getSetting",testDate).$("fileLastModified",testDate).$("settingExists",true); interceptor.postProcess(getMockRequestContext()); assertEquals( 0, arrayLen(mockController.$callLog().setColdboxInitiated) ); // Test it works interceptor.$("getSetting",testDate).$("fileLastModified",testDate+10).$("settingExists",true); interceptor.postProcess(getMockRequestContext()); assertEquals( 1, arrayLen(mockController.$callLog().setColdboxInitiated) ); } </cfscript> </cfcomponent>
New Targeted Model Testing
You can now test model objects directly with no need of doing integration testing. This way you can unit test model objects very very easily using great mocking capabilities. All you need to do is the following:
- Create a test class that inherits from coldbox.system.testing.BaseModelTest
- Create a component annotation called model that equals the full path of the model object CFC to target for unit testing
This testing support class will create your model object, and decorate with mocking capabilities via MockBox and create some mocking classes you might find useful in your model object unit testing. The following are the objects that are placed in the variables scope for you to use:
- model : The target model object to test
- mockLogger : A mock logger class
- mockLogBox : A mock LogBox class
Basic Setup
/** * @model myApp.model.User */ component extends="coldbox.system.testing.BaseModelTest"{ function setup(){ super.setup(); user = model; } function testisActive(){ assertEquals( false, user.isActive() ); } }
Performance Updates
- Lock optimizations on ColdBox dispatcher have improved performance and avoids lock downs.
- Railo now runs ColdBox in multi-threaded mode
- preEvent security optimized
- SES expressions are now evaluated at startup instead of in EACH request, extreme improvement on performance for SES translations.
Categories:
SideBar
User Login
Comments (