Escape Keys - TomdeMan's Blog

The second release of TransferSync, didn't see any bug fixes. Instead, this edition includes some optimizations for those using Coldbox. There are no major changes to the methodology or function. Everything works the same. However, a few things have been updated to take advantage of the Coldbox cache and features to provide better performance.

Grab the latest version at RIAForge.org

I was using the Coldbox SES interceptor and the built in Coldcourse integration, and ran into a little snag the other day. Our production boxes are clustered behind a load balancer using sticky sessions. In order to, reinitialize the framework, I had to access each host by ip and port.

This causes an issue with the current integration with Coldcourse and the SES interceptor. What happens is your BaseURL gets set to 'http://localhost:1234'. Which is no good for those rewrites, since the address will not resolve.

The answer...

Use the config or environments.xml to specify a new setting 'BaseHostName'.

config.xml
<Setting name="BaseHostName"               value="www.mysite.com" />

environments.xml
<environment name="local" urls="" patterns=".local">
      <Setting name="BaseHostName"               value="mysite.local" />

While you are in the config, make sure you check the order of your interceptors. You will want the environment interceptor to come before the SES. Be sure to set the FireOnInit property to TRUE. This means that the load up of settings occurs once the interceptor get's created and NOT on the execution point.

<Interceptor class="interceptor.environmentControl">
         <Property name='configFile'>config/environments.xml.cfm</Property>
         <Property name='fireOnInit'>true</Property>
      </Interceptor>
      
      <Interceptor class="coldbox.system.interceptors.ses">
         <Property name="configFile">config/routes.cfm</Property>
      </Interceptor>

Then, modify the routes.cfm to accept the new setting.

<cfif not settingExists('BaseHostName')>
   <cfset setSetting('BaseHostName',cgi.HTTP_HOST) />
</cfif>

<cfif len(getSetting("AppMapping")) lte 1>
   <cfset setBaseURL("http://#getSetting('BaseHostName')#")>
<cfelse>
   <cfset setBaseURL("http://#getSetting('BaseHostName')#/#getSetting('AppMapping')#/index.cfm") />
</cfif>

That's it. Now whether you are clustered or not, you have control over the host name. Maybe this will make it into a future release of Coldbox.

Thanks to Oscar and Rob for the quick feedback. Both pointed out flaws that needed addressing immediately. I made the appropriate updates, and after a quick review with Luis, even more.

This release should be as optimized as it gets.

Oscar, indicated the importance of getting the accurate hostname when in a clustered environment. He suggested doing so with:

hostname = CreateObject("java", "java.net.InetAddress").getLocalHost().getHostName();

Which from my research, is the best approach. However, as Rob mentioned in his comment, the idea of creating an object on every exception could create a nasty performance drain. Well, we all know Luis has packed Coldbox with all kinds of goodies, and of course in his 'utilities' plugin he already had a method to getInetHost(), which does exactly what Oscar suggested. Except, since its a CB plugin, its already loaded and cached for us. So I now call a cached object's method when the BugLog plugin initializes and I store it in the instance data.

Next, had to get rid of the CFINVOKE, and not by replacing it with a CreateObject. The best way to do this is with lazy loading. You don't want to create and stash the webservice object in the plugin instance at creation, because CB plugins are initialized with the framework. Lazy loading will create the object the first time it is needed, and will persist as long as the plugin itself exists. Which you can define with the CACHE settings in the plugin metadata.

This is what the latest version looks like:

<cfcomponent hint="Passes Exceptions and Bug Reports to BugLog" extends="coldbox.system.plugin" output="false" cache="true" cachetimeout="20">

   <cffunction name="init" access="public" returntype="BugLog" output="false">
      <cfargument name="controller" type="any" required="true">
      
      <cfset super.Init(arguments.controller) />
      <cfset setPluginName("BugLog")>
      <cfset setPluginVersion("1.2")>
      <cfset setPluginDescription("This is the BugLog plugin.")>
      <cfset setHostName(getPlugin('utilities').getInetHost()) />
      
      <cfreturn this>
   </cffunction>
   
   <!--- creates and logs bug report --->
   <cffunction name="logError" access="public" output="No" returntype="boolean">
      <cfargument name="oExceptionBean" required="true" type="any" />
      <cfargument name="sMessage" required="true" type="string" />
      <cfargument name="sSseverityCode" type="string" default="error" />
      
      <cfparam name="session.cfid" default="" />
      <cfparam name="session.cftoken" default="" />
      
      <cfreturn getBugLogWS().logEntry(
                           dateTime = now(),
                           message = arguments.sMessage,
                           applicationCode = application.applicationname,
                           severityCode = arguments.sSseverityCode,
                           hostName = getHostName(),
                           exceptionMessage = arguments.oExceptionBean.getMessage(),
                           exceptionDetails = arguments.oExceptionBean.getDetail(),
                           CFID = session.CFID,
                           CFTOKEN = session.CFTOKEN,
                           userAgent = cgi.HTTP_USER_AGENT,
                           templatePath = cgi.SCRIPT_NAME,
                           HTMLReport = getController().getExceptionService().renderBugReport(arguments.oExceptionBean)
                           ) />

   </cffunction>
   
   <cffunction name="getBugLogWS" access="public" returntype="any" output="false">
      <cfif not structKeyExists(instance,'oBugLogWS')>
         <cfset setBugLogWS(getPlugin('webservices').getWSobj('BugLog')) />
      </cfif>
      <cfreturn instance.oBugLogWS />
   </cffunction>
   
   <cffunction name="setBugLogWS" access="public" returntype="void" output="false">
      <cfargument name="oBugLogWS" type="any" required="true" />
      <cfset instance.oBugLogWS = arguments.oBugLogWS />
   </cffunction>
   
   <cffunction name="getHostName" access="public" returntype="string" output="false">
      <cfreturn instance.sHostName />
   </cffunction>
   
   <cffunction name="setHostName" access="public" returntype="void" output="false">
      <cfargument name="sHostName" type="string" required="true" />
      <cfset instance.sHostName = arguments.sHostName />
   </cffunction>
   
</cfcomponent>

If you didn't notice, I now allow for you to pass in the Message, and Severity Code, along with the Exception Bean. The code in your exception handler should look something like this:

getPlugin('BugLog',true).logError(
                                 oExceptionBean = exceptionBean,
                                 sMessage = exceptionBean.getMessage(),
                                 sSeverityCode = 'onException');

Hope this helps out, I will be updating the original post. Be sure to check that out if you haven't it contains useful information for those who are just getting this setup.

I recently posted about the Coldbox 2.6 environment interceptor and how it allows you to set different config settings for your app. Then, I released a BugLogHQ plugin for Coldbox. After taking in some feedback, and chatting with Luis, I have made some modifications. This post will discuss the updated Webservice Plugin. I will post another to go over the updates to the BugLog plugin.

Currently the native Webservice support for Coldbox expects 2 WSDL URLs for each service, one for DEV and one for PROD. Looks like:

<WebServices>
      <WebService name="TESTWS" URL="http://www.test.com/test.cfc?wsdl" DevURL="http://dev.test.com/test.cfc?wsdl" />
   </WebServices>

Well, with the new environment specific settings, it defeats the purpose of defining them like this any longer. You can now set them in your environemnts.xml. Like so:

<Setting name="Webservices" value="{'BugLog': 'http://bugs.blush.com/listeners/bugLogListenerWS.cfc?wsdl'}" />

Luis plans to deprecate the current method, but not sure as to when. So implementing this can be done in a number of ways in the meantime.

First option: Overwrite existing Webservice plugin distributed with Coldbox and move your Webservice declarations out of the coldbox config file and into your environments.xml. ** You will not have to modify any application code **

Second Option: Place the updated version into your plugins folder, and call it by using getPlugin('webservices',true). This is for those who need some time to update for the deprecation. You can continue to use your existing Webservices from the coldbox config. Allowing you to add new ones to your environments.xml but remember to use the ',true' when calling the webserivce plugin.

Honestly, I recommend the first option, as it shouldn't take much effort to transfer Webservice WSDLs to the environments.xml, and you wont create a mesh of syntax with getPlugin() calls.

<cfcomponent name="webservices"
          hint="The webservices framework plugin."
          extends="coldbox.system.plugin"
          output="false"
          cache="true">


<!------------------------------------------- CONSTRUCTOR ------------------------------------------->

   <cffunction name="init" access="public" returntype="webservices" output="false">
      <cfargument name="controller" type="any" required="true">
      <cfset super.Init(arguments.controller) />
      <cfset setpluginName("Web Services")>
      <cfset setpluginVersion("1.0.a")>
      <cfset setpluginDescription("This is a very useful web services utility plugin.")>
      <cfreturn this>
   </cffunction>

<!------------------------------------------- PUBLIC ------------------------------------------->

   <cffunction name="getWS" returntype="any" access="Public" hint="Get a web service's wsdl url from the configStruct according to which environment you are on." output="false">
   <!--- ************************************************************* --->
      <cfargument name="name" hint="The name of the web service. If the web service is not found an exception is thrown." type="string" required="Yes">
   <!--- ************************************************************* --->
      <cfset var stLocal = structNew() />
      <cfset stLocal.stWSVC = getController().getSetting("WebServices") />
      <cfif structKeyExists(stLocal.stWSVC, arguments.name)>
         <cfreturn stLocal.stWSVC[arguments.name] />
      </cfif>
      <cfthrow type="ColdBox.plugins.webservices.WebServiceNotFoundException" message="The webservice #arguments.name# was not found in the configuration structure.">
   </cffunction>

   <!--- ************************************************************* --->

   <cffunction name="getWSobj" access="Public"   hint="Get a reference to a webservice obj according to which environment you are on." output="false" returntype="any">
   <!--- ************************************************************* --->
      <cfargument name="name" hint="The name of the web service. If the web service is not found an exception is thrown" type="string" required="Yes">
   <!--- ************************************************************* --->
      <cfset var stLocal = structNew() />
      <cfset stLocal.stWSVC = getController().getSetting("WebServices") />
      <cfif structKeyExists(stLocal.stWSVC, arguments.name)>
         <cfreturn CreateObject("webservice", stLocal.stWSVC[arguments.name] )>
      </cfif>
      <cfthrow type="ColdBox.plugins.webservices.WebServiceNotFoundException" message="The webservice #arguments.name# was not found in the configuration structure.">
   </cffunction>

   <!--- ************************************************************* --->

   <cffunction name="refreshWS" access="Public" hint="Refresh a web service stub object" output="false" returntype="void">
   <!--- ************************************************************* --->
      <cfargument name="webservice" hint="The name or wsdl URL of the web service to refresh" type="string" required="Yes">
   <!--- ************************************************************* --->
      <!--- Get the Webservice from the configStruct --->
      <cfset var ws = getWS(arguments.webservice)>
      <cfset var rpcService = "">
      <cfset var factory = 0>
      
      <cfif ws neq "">
         <cfobject type="java" action="create" name="factory" class="coldfusion.server.ServiceFactory">
         <cfset rpcService = factory.XmlRpcService>
         <cfset rpcService.refreshWebService(ws)>
      <cfelse>
         <cfobject type="java" action="create" name="factory" class="coldfusion.server.ServiceFactory">
         <cfset rpcService = factory.XmlRpcService>
         <cfset rpcService.refreshWebService(arguments.webservice)>
      </cfif>
   </cffunction>

   <!--- ************************************************************* --->

</cfcomponent>

BugLog and Coldbox Plugin

If you have heard of BugLogHQ, a nice centralized bug reporting application, written by Oscar Arevalo, and you want to integrate it with your Coldbox apps, then this article is for you.

Let's get started...

Be sure to set the Exception Handler in either the Coldbox config or your Environments.xml. For reference, 'main' is my handler and 'onException' is the method or event. By default, the value is blank in the config.

<Setting name="ExceptionHandler"         value="main.onException" />

Next, you need to add the WSDL URL to your Coldbox config file or your Environments.xml.

<Setting name="BugLogWSDL"                   value="http://bugs.mysite.com/listeners/bugLogListenerWS.cfc?wsdl" />

@UPDATE - ** WEBSERVICE PLUGIN UPDATED **

Now, go to that Exception Handler method we defined in the first step. Copy and paste this in there.

@UPDATE - UPDATED CODE HERE

Finally, create the BugLog.cfc and place it in your Plugins directory ('plugins' by default unless you specified otherwise in your CONVENTIONS).

Here's the code.

@UPDATE - UPDATED CODE HERE

That's it.

Now, Coldbox will deliver Bug Reports to the BugLog system, in addition to the internal Bug Reporting setting. If you don't want to use CB to deliver Email Reports then be sure to define that in your config or environments.xml.

<Setting name="EnableBugReports"          value="false" />

We know how to use the Coldbox environment interceptor to load different values for datasources. We know how to circumvent datasource.xml files with Transfer by using a datasource bean. Now, how can we use ColdSpring to put it all together for us.

<!-- coldbox -->
   <bean id="ColdboxFactory" class="coldbox.system.extras.ColdboxFactory" />
   <bean id="Coldbox" factory-bean="ColdBoxFactory" factory-method="getColdbox" singleton="true" />
   
   <!-- transfer -->
   <bean id="TransferFactory" class="transfer.TransferFactory" singeleton="true">
      <constructor-arg name="configuration">
         <bean class="model.TransferConfig">
            <constructor-arg name="DSNBean">
               <bean factory-bean="ColdBoxFactory" factory-method="getDatasource">
                  <constructor-arg name="alias">
                     <value>${Transfer_DSNAlias}</value>
                  </constructor-arg>
               </bean>
            </constructor-arg>
            <constructor-arg name="configPath">
             <value>${Transfer_ConfigPath}</value>
            </constructor-arg>
            <constructor-arg name="definitionPath">
             <value>${Transfer_DefinitionPath}</value>
            </constructor-arg>
         </bean>
       </constructor-arg>
   </bean>
      
   <bean id="Transfer" factory-bean="TransferFactory" factory-method="getTransfer" singleton="true" />
   <bean id="Datasource" factory-bean="TransferFactory" factory-method="getDatasource" singleton="true" />

That's what my coldspring.xml file would look like. There are a few things to note. I use Coldbox to get the Datasource information I need because it has already been defined in the Coldbox config or environment file. This is the reason I wanted to avoid a datasource.xml file for Transfer. Otherwise, I'd have the same DSN info repeated all over the place.

I also point to model.TransferConfig. A simple extension of the Transfer Configuration bean. You can learn more about that from my previous post.

If you notice, there is a Transfer_DSNAlias argument being passed into the Coldbox getDatasource method. This tells CB which DSN you want to load up and pass on to Transfer. Coldbox allows for multiple datasources to be stored for use in your App. But Transfer only connects to one at a time. So I made it a setting in the config/environment.

Here's a peak at my environements.xml file.

<environment name="development" urls="" patterns=".local">
   <Setting name="DebugMode"               value="true" />
   <Setting name="DebugPassword"               value="" />
   <Setting name="ReinitPassword"               value="" />
   <Setting name="EnableDumpVar"               value="true" />
   <Setting name="HandlersIndexAutoReload"          value="true" />
   <Setting name="ConfigAutoReload"            value="true" />
   <Setting name="HandlerCaching"               value="false" />
   <Setting name="EventCaching"               value="false" />
   
   <Setting name="Datasources" value="{'MyDSNAlias': {'Alias': 'MyDSNAlias' , 'Name': 'MyDSN', 'DBType': 'mssql', 'Username': 'dbusername', 'Password': 'dbpass'}}" />
   
   <Setting name="Transfer_ConfigPath" value="/config/transfer.xml.cfm" />
   <Setting name="Transfer_DefinitionPath" value="/model/definition" />
   <Setting name="Transfer_DSNAlias" value="MyDSNAlias" />
</environment>

That's it.

I talked previously about the abilities of the new Environment Interceptor in Coldbox. The next step was to wire it up so I could automate the initialization of Transfer using ColdSpring and these environment specific values from Coldbox.

Before I get into that I needed to know how to overwrite complex values, like Datasources, set in the Coldbox config file with those from the environments.xml.

Luis was kind enough to point me in the right direction, and said dump the ConfigSettings struct and Transverse whatever you want. That I did, using JSON notation. Just substitute single quotes for double quotes maintaining valid XML format.

<environment name="development" urls="" patterns=".local">
   <Setting name="DebugMode"                  value="true" />
   <Setting name="DebugPassword"               value="" />
   <Setting name="ReinitPassword"               value="" />
   <Setting name="EnableDumpVar"               value="true" />
   <Setting name="HandlersIndexAutoReload"         value="true" />
   <Setting name="ConfigAutoReload"            value="true" />
   <Setting name="HandlerCaching"               value="false" />
   <Setting name="EventCaching"               value="false" />
   
   <Setting name="Datasources" value="{'MyDSNAlias': {'Alias': 'MyDSNAlias' , 'Name': 'MyDSN', 'DBType': 'mssql', 'Username': 'dbusername', 'Password': 'dbpass'}}" />
</environment>

The latest release of Coldbox 2.6.0 RC1 is like every other Coldbox release...Filled with goodies.

For now, I am keying in on the Environment Interceptor. It allows you to specify a different set of configuration settings based on the environment your application is running in. You are no longer limited to a single config file and the settings bound to it.

By default, Luis uses URLS specified in the environments.xml and looks for them in the cgi.http_host. While it is effective, I have always liked the flexibility of regular expressions.

I have updated the code to accept a PATTERNS attribute from the config xml file and use the pattern as a regular expression against the cgi.http_host.

It extends the environmentControl interceptor that is distributed with CB, and is completely backward compatible.

Get the CODE - EnvironmentControl.zip

If you want to use the PATTERNS leave the URLS empty.

I thought about using both URLS and PATTERNS together, but with regEx you can pretty much capture any URL.

The environments.xml.cfm would look like:

<?xml version="1.0" encoding="ISO-8859-1"?>
<environmentcontrol>
   <environment name="development" urls="" patterns=".local">
      <Setting name="DebugMode"                  value="true" />
      <Setting name="DebugPassword"               value="" />
      <Setting name="ReinitPassword"               value="" />
      <Setting name="EnableDumpVar"               value="true" />
      <Setting name="HandlersIndexAutoReload"         value="true" />
      <Setting name="ConfigAutoReload"            value="true" />
      <Setting name="HandlerCaching"               value="false" />
      <Setting name="EventCaching"               value="false" />
   </environment>
</environmentcontrol>

The interceptor in the config.xml.cfm would look like:
(only difference here is the class path - no longer points to coldbox.system.interceptors)

<Interceptor class="interceptor.environmentControl">
   <Property name='configFile'>config/environments.xml.cfm</Property>
   <Property name='fireOnInit'>false</Property>
</Interceptor>

For more on the latest release, check out What's New in Coldbox 2.6.0

Using Custom Plugins and Coldbox

After some head rattle and some wise words from Team Coldbox, they have explained to me that Plugins in Coldbox serve there purpose of 'enhancing and creating reusability'.

But will soon be able to plug in at certain 'execution points'. Coldbox and AOP in the next release. Nice!

You may think Coldbox already has a few execution point with the PreHandler/PostHandler methods within the handlers(controller) themselves. But they are limited to specific handlers.

Coldbox also allows you to designate code at the start and finish of every request using the OnRequestStartHandler RequestEndHandler event defined in your config.xml. And on a broader level there's an ApplicationStartHandler available in there to.

Depending on the impact of the plugin placement is for you to decide.

But for an example let's use the idea of securing our apps. And we have Accounts withe Roles attached to them. The Roles are also tied to individual events and entire handlers depending on access.

So what should you do?

Well, if you want to 'follow standards', create a plugin and call it with the OnRequestStartHandler. 'Don't throw methods anywhere, make sure they have an identity.' Keeping in mind encapsilation and trying to avoid dependecy. It's those OO fundamentals again.

But sometimes its easy to slip.

For example, why not just throw an method into the securityService and call that from the PreHandler in those handlers that have events that need to be secured?

Well for starters, what if the user didn't have access to run the event, or better yet any of the events in that handler. You could have avoided even calling upon this handler. If you had checked at the beginning of the request using the OnRequestStartHandler you could have deciphered that there was no need to make a call to that handler.

Theres another problem when you check from within the handler. You have created a dependency. That handler now needs that securityService to be there. Not good. If you had to, at least use use a plugin here. The handlers have native access to those with Coldbox. But remember, you could have stopped yourself from even getting this far.

Now let's tighten things up a bit. Forget about making all these scattered calls to the securityService. Realize the reusability for a security module, and that its nature is that of a 'plugin'. The purpose is to handle calls to the securityService at some point in the Request Life Cycle, call it an Interceptor. Create yourself a securityInterceptor plugin, and of course there's a doc for writing a plugin at the

Use this to create a layer between the Coldbox framework and your custom securityService. Your plugin will be generic enough to be shared with other Coldbox apps, and the app specific code will belong in the service. So when a change comes along you're only modifying your service layer.

** I am in the process of cleaning up my code examples for this. So check back in a few. **

Coldbox Install and Cookie Datasource Error

If you are using ColdBox and run into the missing Cookie Datasource error, it's most likely because you have your Cookies set to NONE in the ColdFusion Administrator and the Debug Console ENABLED in the ColdBox config. Toggle one of your settings and you'll be back in business.

Coldbox Config File:

<Setting name="DebugMode" value="true" />