Using Hibernate with ColdFusion, part 3

In previous two parts of this article we have covered creating base HibernateProxy.cfc and JavaBuilder.cfc. It is time to add ability to load Java class from HibernateProxy.cfc.

In order to do this we need one new method in HibernateProxy.cfc called ensureLoaded(). This method takes CFC instance and checks if it was already loaded. If not it runs JavaBuilder.cfc and adds class to cache. Method is simple as that:

 <cffunction name="ensureLoaded" access="public" returntype="any" output="false">
  <cfargument name="entity" type="WEB-INF.cftags.component" required="true" />
  <cfscript>
   var _name = getMetaData(arguments.entity).NAME;
   var _path = getMetaData(arguments.entity).PATH;
   var _item = structNew();
   var _cfc = "JavaBuilder";
   if (variables.___useCustomJavaBuilder)
    _cfc = variables.___customJavaBuilder;

   if ( not getCache().hasItem( _name ) )
   {
    item.name = _name;
    item.timestamp = now();

    item.clazz = createObject("component", _cfc).build(
     this,
     arguments.entity,
     _name,
     _path
    );
    getCache().setItem( item );
    // configure mapping with hibernate itself:
    // 1. add xml configuration here:
    setConfiguration(
     getConfiguration().addFile(
      replace(_path,listLast(_name,'.')&'.cfc',
              listLast(_name,'.')&'.hbm.xml') ) );
    // 2. rebuild mappings:
    getConfiguration().buildMappings();
    // 3. recreate session factory:
    rebuildSessionFactory();
   }
   return getCache().getItem( _name );
  </cfscript>
 </cffunction>

If there is no class in cache JavaBuilder goes into action. When class returns from JavaBuilder it is cached and returned to caller method. HibernateProxyCache code below:

<cfcomponent>

 <cfset variables.___cache = structNew() />

 <cffunction name="getItem" access="public" returntype="any" output="false">
  <cfargument name="item" type="string" required="true" />
  <cfreturn variables.___cache[arguments.item].clazz />
 </cffunction>

 <cffunction name="setItem" access="public" returntype="void" output="false">
  <cfargument name="item" type="struct" required="true" />
  <cfset variables.___cache[arguments.item.name] = item />
 </cffunction>

 <cffunction name="hasItem" access="public" returntype="boolean" output="false">
  <cfargument name="item" type="string" required="true" />
  <cfreturn structKeyExists( variables.___cache, arguments.item ) />
 </cffunction>

</cfcomponent>

There is still one unexplained piece of code. In first part we have created core implementation of HibernateProxy.cfc. There is code responsible for configuring HiberCF. HiberCF gives the ability of implementing custom JavaBuilder. It may be defined by assigning full component path to the following HiberCF setting:

hibercf.custom_java_builder

When this property is defined in hibercf.properties file HiberCF uses custom JavaBuilder. But custom JavaBuilder is validated to check if required build() method with required arguments and returntype is declared. verifyCustomJavaBuilder() method is placed in HibernateProxyUtils.cfc and its code is below:

<cfcomponent>

 <cffunction name="verifyCustomJavaBuilder" access="public"
  output="false" returntype="boolean">
  <cfargument name="cfcPath" type="string" required="true" />
  <cfset var cfc = "" />
  <cfset var md = structNew() />
  <cfset var i = 0 />
  <cfset var func = structNew() />
  <cftry>
   <cfset cfc = createObject("component", arguments.cfcPath) />
   <cfcatch type="any">
    <cfthrow
     type="HibernateProxy.Utils.customJavaBuilder"
     message="There was an error while intantiating
              custom Java Builder '#arguments.cfcPath#'.
              Check if your component exists and it is working CFC.
              Default JavaBuilder will be used." />
   </cfcatch>
  </cftry>
  <!---
   Verify if there is build() function which returns 'any'
   and takes:
    - proxy: HibernateProxy
    - entity: WEB-INF.cftags.component
    - name: string
    - path: string
  --->
  <cfset md = getMetaData( cfc ) />
  <cfloop from="1" to="#arrayLen(md.functions)#" index="i">
   <cfif lcase(md.functions[i].name) eq "build">
    <cfset func = md.functions[i] />
    <!--- check returntype: --->
    <cfif structKeyExists(func, "returntype")
          and lcase(func.returntype) neq "any">
     <cfthrow
      type="HibernateProxy.Utils.customJavaBuilder"
      message="Custom Java Builder '#arguments.cfcPath#' 'build' has
               incorrect return type defined. Correct is 'any'.
               Default JavaBuilder will be used." />
    </cfif>
    <!--- check number of arguments: --->
    <cfif arrayLen(func.parameters) neq 4>
     <cfthrow
      type="HibernateProxy.Utils.customJavaBuilder"
      message="Custom Java Builder '#arguments.cfcPath#' 'build' has
               incorrect number of arguments. Correct is 4.
               Default JavaBuilder will be used." />
    </cfif>
    <!--- check types of arguments: --->
    <cfif
     lcase(func.parameters[1].type) neq "hibernateproxy"
     or lcase(func.parameters[2].type) neq "web-inf.cftags.component"
     or lcase(func.parameters[3].type) neq "string"
     or lcase(func.parameters[4].type) neq "string"
    >
     <cfthrow
      type="HibernateProxy.Utils.customJavaBuilder"
      message="Custom Java Builder '#arguments.cfcPath#' 'build' has
               incorrect arguments. Correct is
               build(HibernateProxy,WEB-INF.cfcatgs.component,string,string).
               Default JavaBuilder will be used." />
    </cfif>
   </cfif>
  </cfloop>
  <cfthrow
   type="HibernateProxy.Utils.customJavaBuilder"
   message="Custom Java Builder '#arguments.cfcPath#' does not define
           'build' method. Default JavaBuilder will be used." />
 </cffunction>

</cfcomponent>

Short explanation why there is no <cfinterface>. One of the assumptions was to make this code running on ColdFusion 7. <cfinterface> tag was added in ColdFusion 8.

In 4th part we will finally add methods to invoke operations on Hibernate Session. We will also discuss mapping data types from CFML to Java and from Java to CFML.


About this entry