Blog Projects
Escape Keys
A ColdFusion and Web Development Blog by Tom de Manincor
 

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>