Thursday 26 November 2009

GWT Rich Text Editor

Examples of GWT rich text editors are pretty rubbish, so here is a decent one.

It's based on the GWT Showcase Rich Text example, but with the stuff I didn't want taken out and a basic colour picker widget put in.

Also, I replaced the icons with some that I liked better from here.

Check it out:

Run the project with 'mvn clean install tomcat:run-war' and point your brower at http://localhost:8080/richTextEditor

Source Code

Friday 4 September 2009

Coldfusion File Streaming & Stream Proxying

Hot on the heels of yesterdays post about Java Streaming Proxy Servlets comes even more streaming goodness with CF.

The first bit of code opens a file, captures the underlying servlet output stream and then streams out the contents of the file.

streamFile.cfm

<cfsilent>
  <cfif ReFind("[^a-zA-Z0-9_\-\.]", URL.filename)>
    <cfthrow message="not permitted">
  </cfif>

  <cfscript>
    filename = REQUEST.fileDirectory & "/" & URL.filename;
    inputFile = createObject("java", "java.io.File").init(filename);
    inputStream = createObject("java", "java.io.FileInputStream").init(inputFile);
    outputStream = getPageContext().getResponse().getResponse().getOutputStream();

    while(true) {
      b = inputStream.read();

      if(b EQ -1) {
        break;
      }

      outputStream.write(b);
    }
    outputStream.flush();

    inputStream.close();
    outputStream.close();   
  </cfscript>
</cfsilent>

Streaming the contents of a file to a browser is all good because you didn't have to load the whole thing in memory before sending it down the wire. But what if you want to stream a file to a browers that is held on another server, not accessible from the internet. In that case you'll want to stream the file through a proxy to the client.

streamProxy.cfm

<cfsilent>
  <cfheader name="Content-Disposition" value="attachment; filename=#URL.userFilename#">
  <cfcontent type="text/plain">

  <cfif ReFind("[^a-zA-Z0-9_\-\.]", URL.filename)>
    <cfthrow message="not permitted">
  <cfelseif ReFind("[^a-zA-Z0-9_\-\.]", URL.userFilename)>
    <cfthrow message="not permitted">
  </cfif>

  <cfscript>   
    proxy = createObject("java", "org.apache.commons.httpclient.HttpClient").init();

    if(len(CGI.QUERY_STRING) GT 0) {
      query = "?#CGI.QUERY_STRING#";
    } else {
      query = "";
    }

    uri = REQUEST.streamUrl & query;

    proxyMethod = createObject("java", "org.apache.commons.httpclient.methods.GetMethod").init(uri); 
    proxy.executeMethod(proxyMethod); 
    inputStream = proxyMethod.getResponseBodyAsStream(); 
    outputStream = getPageContext().getResponse().getResponse().getOutputStream();
  
    while(true) {
      b = inputStream.read();

      if(b EQ -1) {
        break;
      }

      outputStream.write(b);
    }

    outputStream.flush();

    inputStream.close();
    outputStream.close();
  </cfscript>
</cfsilent>

The proxy uses the Jakarta Commons HttpClient which comes bundled with ColdFusion to call the target stream, passing any URL query parameters supplied.

The response from target tream URL is streamed back to the client via the underlying servlet output stream, just like the file streaming code.

An example project is available for download, make sure to customise the REQUEST scope variables in the Application.cfm file before testing.

Source Code

Thursday 3 September 2009

Streaming Http Proxy Servlet

You know when you just need a streaming http proxy servlet and you just can't find one? Well try this:

HttpProxyServlet.java

package org.adrianwalker.servlet.proxy;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;

public final class HttpProxyServlet extends HttpServlet {

  private URL url;
  private HttpClient proxy;

  @Override
  public void init(final ServletConfig config) throws ServletException {

    super.init(config);

    try {
      url = new URL(config.getInitParameter("url"));
    } catch (MalformedURLException me) {
      throw new ServletException("Proxy URL is invalid", me);
    }
    proxy = new HttpClient();
    proxy.getHostConfiguration().setHost(url.getHost());
  }

  @Override
  protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
          throws ServletException, IOException {

    Map<String, String[]> requestParameters = request.getParameterMap();

    StringBuilder query = new StringBuilder();
    for (String name : requestParameters.keySet()) {
      for (String value : requestParameters.get(name)) {

        if (query.length() == 0) {
          query.append("?");
        } else {
          query.append("&");
        }

        name = URLEncoder.encode(name, "UTF-8");
        value = URLEncoder.encode(value, "UTF-8");

        query.append(String.format("&%s=%s", name, value));
      }
    }

    String uri = String.format("%s%s", url.toString(), query.toString());
    GetMethod proxyMethod = new GetMethod(uri);
    
    proxy.executeMethod(proxyMethod);
    write(proxyMethod.getResponseBodyAsStream(), response.getOutputStream());
  }

  @Override
  protected void doPost(final HttpServletRequest request, final HttpServletResponse response)
          throws ServletException, IOException {

    Map<String, String[]> requestParameters = request.getParameterMap();

    String uri = url.toString();
    PostMethod proxyMethod = new PostMethod(uri);
    for (String name : requestParameters.keySet()) {
      for (String value : requestParameters.get(name)) {
        proxyMethod.addParameter(name, value);
      }
    }

    proxy.executeMethod(proxyMethod);
    write(proxyMethod.getResponseBodyAsStream(), response.getOutputStream());
  }

  private void write(final InputStream inputStream, final OutputStream outputStream) throws IOException {
    int b;
    while ((b = inputStream.read()) != -1) {
      outputStream.write(b);
    }

    outputStream.flush();
  }

  @Override
  public String getServletInfo() {
    return "Http Proxy Servlet";
  }
}

This class makes use of the Jakarta Commons HttpClient to forward requests to a configurable url and streams back the response.

A sample project using this servlet is available for download below. The servlet acts as a proxy for a google search, returning the search results page. The proxied url is configurable in the web applications web.xml file. The project builds a war and uses the jetty plugin so you can test the code.

Run the project with 'mvn clean install jetty:run-war' and point your brower at http://localhost:8080/http-proxy-servlet

Source Code

Friday 28 August 2009

Maven GWT WAR JPA Jetty & Derby skeleton project.

Thats right, all that good stuff in one skeleton project.

Here is the pom.xml for the project:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <groupId>org.adrianwalker.maven.skeleton.war.jpa.gwt</groupId>
  <artifactId>maven-war-gwt-jpa-derby-skeleton</artifactId>
  <packaging>war</packaging>
  <version>0.1.0</version>
  <name>Maven WAR-GWT-JPA-Derby Skeleton</name>

  <description>
    Skeleton project for creating GWT WAR running on Jetty, with a Derby
    database accessed through JPA.

    Usage: mvn clean install jetty:run-war
  </description>

  <url>http://www.adrianwalker.org</url>

  <organization>
    <name>adrianwalker.org</name>
    <url>http://www.adrianwalker.org</url>
  </organization>

  <developers>
    <developer>
      <name>Adrian Walker</name>
      <email>ady.walker@gmail.com</email>
      <organization>adrianwalker.org</organization>
      <organizationUrl>http://www.adrianwalker.org</organizationUrl>
    </developer>
  </developers>

  <repositories>
    <repository>
      <id>java.net</id>
      <url>http://download.java.net/maven/1</url>
      <layout>legacy</layout>
    </repository>
    <repository>
      <id>codehaus.org</id>
      <url>http://repository.codehaus.org</url>
    </repository>
  </repositories>

  <properties>
    <gwt.version>1.7.0</gwt.version>
  </properties>

  <build>
    <outputDirectory>war/WEB-INF/classes</outputDirectory>

    <resources>
      <resource>
        <directory>${basedir}/src/main/resources</directory>
        <includes>
          <include>**/*</include>
        </includes>
      </resource>
    </resources>

    <plugins>
      <plugin>
        <artifactId>maven-clean-plugin</artifactId>
        <configuration>
          <filesets>
            <fileset>
              <directory>war</directory>
              <includes>
                <include>**/*</include>
              </includes>
            </fileset>
          </filesets>
        </configuration>
      </plugin>

      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>gwt-maven-plugin</artifactId>
        <version>1.1</version>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.0.2</version>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>maven-jetty-plugin</artifactId>
        <version>6.1.9</version>
      </plugin>
    </plugins>
  </build>
  
  <dependencies>
    <dependency>
      <groupId>com.google.gwt</groupId>
      <artifactId>gwt-servlet</artifactId>
      <version>${gwt.version}</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>com.google.gwt</groupId>
      <artifactId>gwt-user</artifactId>
      <version>${gwt.version}</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>toplink.essentials</groupId>
      <artifactId>toplink-essentials</artifactId>
      <version>2.0-36</version>
    </dependency>
    <dependency>
      <groupId>org.apache.derby</groupId>
      <artifactId>derby</artifactId>
      <version>10.4.2.0</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.4</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  <reporting>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-checkstyle-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-javadoc-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>cobertura-maven-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>jxr-maven-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-pmd-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>findbugs-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </reporting>
</project>

The major pain with this was getting a jndi accessible data source working with Jetty and JPA. Check out the persistence.xml and jetty-env.xml for the correct configuration:

jetty-env.xml

<?xml version="1.0"  encoding="ISO-8859-1"?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.mortbay.org/configure.dtd">
<Configure class="org.mortbay.jetty.webapp.WebAppContext">
  <New id="message" class="org.mortbay.jetty.plus.naming.Resource">
    <Arg>jdbc/message</Arg>
    <Arg>
      <New class="org.apache.derby.jdbc.EmbeddedDataSource">
        <Set name="DatabaseName">target/database/message</Set>
        <Set name="user"></Set>
        <Set name="password"></Set>
        <Set name="createDatabase">create</Set>
      </New>
    </Arg>
  </New>
</Configure>

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="persistenceUnit" transaction-type="RESOURCE_LOCAL">
    <provider>oracle.toplink.essentials.PersistenceProvider</provider>
    <non-jta-data-source>java:comp/env/jdbc/message</non-jta-data-source>
    <class>org.adrianwalker.maven.skeleton.jpa.entity.MessageEntity</class>
    <properties>
      <property name="toplink.ddl-generation" value="create-tables"/>
    </properties>
  </persistence-unit>
</persistence>

Run the project with 'mvn clean install jetty:run-war' and point your brower at http://localhost:8080

Source Code

Thursday 30 July 2009

Maven GWT WAR skeleton project.

Here is the second in a series of maven project skeletons. This project creates a WAR file containing a GWT default application.

This eclipse project is constructed to make use of the Maven and GWT plugins.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <groupId>org.adrianwalker.maven.skeleton.war</groupId>
  <artifactId>maven-war-skeleton</artifactId>
  <packaging>war</packaging>
  <version>0.1.0</version>
  <name>Maven WAR Skeleton</name>
  <description>Skeleton project for creating GWT WARs</description>

  <url>http://www.adrianwalker.org</url>

  <organization>
    <name>adrianwalker.org</name>
    <url>http://www.adrianwalker.org</url>
  </organization>

  <developers>
    <developer>
      <name>Adrian Walker</name>
      <email>ady.walker@gmail.com</email>
      <organization>adrianwalker.org</organization>
      <organizationUrl>http://www.adrianwalker.org</organizationUrl>
    </developer>
  </developers>

  <properties>
    <gwt.version>1.7.0</gwt.version>
  </properties>

  <build>
    <outputDirectory>war/WEB-INF/classes</outputDirectory>

    <plugins>
      <plugin>
        <artifactId>maven-clean-plugin</artifactId>
        <configuration>
          <filesets>
            <fileset>
              <directory>war</directory>
              <includes>
                <include>**/*</include>
              </includes>
            </fileset>
          </filesets>
        </configuration>
      </plugin>

      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>gwt-maven-plugin</artifactId>
        <version>1.1</version>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins
        </groupId>
        <artifactId>maven-compiler-plugin
        </artifactId>
        <version>2.0.2</version>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
    </plugins>
  </build>

  <dependencies>
    <dependency>
      <groupId>com.google.gwt</groupId>
      <artifactId>gwt-servlet</artifactId>
      <version>${gwt.version}</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>com.google.gwt</groupId>
      <artifactId>gwt-user</artifactId>
      <version>${gwt.version}</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  <reporting>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-checkstyle-plugin</artifactId>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-javadoc-plugin</artifactId>
      </plugin>

      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>cobertura-maven-plugin</artifactId>
      </plugin>

      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>jxr-maven-plugin</artifactId>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-pmd-plugin</artifactId>
      </plugin>

      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>findbugs-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </reporting>
</project>

Source Code

Monday 27 July 2009

Maven executable JAR skeleton assembly project.

Maven project wizards are ok, but you still end up editing the POM a fair amount to get exactly what you need. It'd be useful just to have some skeleton projects with 90% of what you want ready to go.

Here is the first in a series of maven project skeletons. This project creates an executable JAR and a ZIP assembly to distribute it, along with its dependancies, config files and batch files etc.

The pom.xml includes various report generation plugins and references an assembly configuration, distribution.xml:

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.adrianwalker.maven.skeleton.jar</groupId>
  <artifactId>maven-jar-skeleton</artifactId>
  <packaging>jar</packaging>
  <version>0.1.0</version>
  <name>Maven JAR Skeleton</name>
  <description>Skeleton project for creating executable JARs</description>
  <url>http://www.adrianwalker.org</url>

  <organization>
    <name>adrianwalker.org</name>
    <url>http://www.adrianwalker.org</url>
  </organization>

  <developers>
    <developer>
      <name>Adrian Walker</name>
      <email>ady.walker@gmail.com</email>
      <organization>adrianwalker.org</organization>
      <organizationUrl>http://www.adrianwalker.org</organizationUrl>
    </developer>
  </developers>

  <build>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>

      <plugin>
        <artifactId>maven-jar-plugin</artifactId>
        <configuration>
          <archive>
            <manifest>
              <addClasspath>true</addClasspath>
              <mainClass>org.adrianwalker.maven.skeleton.jar.Main</mainClass>
            </manifest>
            <manifestEntries>
              <Class-Path>../conf/</Class-Path>
            </manifestEntries>
          </archive>
        </configuration>
      </plugin>

      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <configuration>
          <descriptors>
            <descriptor>src/main/assembly/distribution.xml</descriptor>
          </descriptors>
        </configuration>
        <executions>
          <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
              <goal>assembly</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>

    <resources>
      <resource>
        <directory>src/main/resources</directory>
        <excludes>
          <exclude>**/*</exclude>
        </excludes>
      </resource>
    </resources>
  </build>

  <reporting>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-checkstyle-plugin</artifactId>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-javadoc-plugin</artifactId>
      </plugin>

      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>cobertura-maven-plugin</artifactId>
      </plugin>

      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>jxr-maven-plugin</artifactId>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-pmd-plugin</artifactId>
      </plugin>

      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>findbugs-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </reporting>

  <dependencies>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.5.6</version>
    </dependency>

    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.5.6</version>
    </dependency>

    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.14</version>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

distribution.xml

<assembly>
  <id>distribution</id>
  <formats>
    <format>zip</format>
  </formats>

  <files>
    <file>
      <source>/src/main/resources/bin/run.bat</source>
      <outputDirectory>/bin</outputDirectory>
      <filtered>true</filtered>
    </file>
  </files>

  <fileSets>
    <fileSet>
      <directory>/src/main/resources/conf</directory>
      <outputDirectory>/conf</outputDirectory>
      <includes>
        <include>*.properties</include>
      </includes>
    </fileSet>
    <fileSet>
      <directory>/target</directory>
      <outputDirectory>/lib</outputDirectory>
      <includes>
        <include>*.jar</include>
      </includes>
    </fileSet>
  </fileSets>
  
  <dependencySets>
    <dependencySet>
      <outputDirectory>/lib</outputDirectory>
    </dependencySet>
  </dependencySets>
</assembly>

Source Code

Tuesday 21 July 2009

AJAX Reference

This is more a quick reference for me than anything else. Below is a CFML'ed up version of the AJAX tutorial from w3schools.com.

index.cfm

<html>
  <head>
    <script src="js/script.js" type="text/javascript"></script>
  </head>
  <body>
    <form name="myForm">
      Name: <input id="username" type="text" onkeyup="ajaxFunction();" />
      Time: <input id="time" type="text" />
    </form>
  </body>
</html>

time.cfm

<cfoutput>#timeFormat(now())#</cfoutput>

script.js

function ajaxFunction() {
  var xmlhttp;
  if (window.XMLHttpRequest) {
    // code for IE7+, Firefox, Chrome, Opera, Safari
    xmlhttp=new XMLHttpRequest();
  } else if (window.ActiveXObject) {
    // code for IE6, IE5
    xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
  } else {
    alert("Your browser does not support XMLHTTP!");
  }
  
  xmlhttp.onreadystatechange=function() {
    if(xmlhttp.readyState==4) {
      document.myForm.time.value=xmlhttp.responseText;
    }
  }
  xmlhttp.open("GET", "time.cfm", true);
  xmlhttp.send(null);
}

Prototype

And to achieve the same thing with the prototype library:

index.cfm

  ...
  <head>
    ...
    <script src="js/prototype-1.6.0.3.js" type="text/javascript"></script>
    ...
  </head>
  ...

time.cfm - as above

script.js

function ajaxFunction() {
  var request = new Ajax.Request("time.cfm", {
    method: "get",
    onComplete: function(xmlhttp){
      $('time').value = xmlhttp.responseText;
    }
  });
}

Thursday 16 July 2009

Simple MVC

"Frameworks just complicate things and force you to do their way"
"We don't need Model-View-Controller, we don't do OOP"

Quotes like that proper annoy me. If you want to program unmaintainable procedural spaghetti code on a terminal in vi, then go back to programming in the 70's where you belong.

MVC is just a design pattern. Not being arsed with using an MVC framework on a project doesn't mean you can't write well designed, loosely coupled, easy to maintain programs.

Business logic and state should be encapsulated by your model. Below is a model component for paging through the CFARTGALLERY database that comes with Coldfusion 8:

Model.cfc

<cfcomponent output="false">

  <cffunction access="public" returntype="Model" name="init">
    <cfargument required="true" type="Data" name="data">
    <cfset VARIABLES.data = ARGUMENTS.data>
    <cfreturn this>
  </cffunction>

  <cffunction access="public" returntype="numeric" name="getArtCount">
    <cfreturn VARIABLES.data.countArt().count>
  </cffunction>

  <cffunction access="public" returntype="string" name="getArtNames">

    <cfset var LOCAL = {}>

    <cfparam name="SESSION.firstRecord" default=1>
    <cfparam name="SESSION.maxRecords" default=10>

    <cfset LOCAL.query = VARIABLES.data.findArt(SESSION.firstRecord, SESSION.maxRecords)>
    <cfreturn ValueList(LOCAL.query.artName)>
  </cffunction>

  <cffunction access="public" returntype="boolean" name="showPrev">
    <cfif SESSION.firstRecord GT 1>
      <cfreturn true>
    <cfelse>
      <cfreturn false>
    </cfif>
  </cffunction>

  <cffunction access="public" returntype="boolean" name="showNext">
    <cfif SESSION.firstRecord + SESSION.firstRecord LT getArtCount()>
      <cfreturn true>
    <cfelse>
      <cfreturn false>
    </cfif>
  </cffunction>

  <cffunction access="public" returntype="void" name="nextPage">
    <cfset SESSION.firstRecord = SESSION.firstRecord + SESSION.maxRecords>
  </cffunction>

  <cffunction access="public" returntype="void" name="prevPage">
    <cfset SESSION.firstRecord = SESSION.firstRecord - SESSION.maxRecords>
  </cffunction>
</cfcomponent>

Database code is abstracted to a layer encapsulated by the model, in it's own Data CFC:

Data.cfc

<cfcomponent output="false">

  <cfset VARIABLES.datasource = "cfartgallery">

  <cffunction access="public" returntype="Data" name="init">
    <cfreturn this>
  </cffunction>

  <cffunction access="public" returntype="query" name="countArt">
    <cfset var LOCAL = {}>
    <cfquery name="LOCAL.countArt" datasource="#datasource#">
      SELECT COUNT(*) AS count FROM art
    </cfquery>
    <cfreturn LOCAL.countArt>
  </cffunction>

  <cffunction access="public" returntype="query" name="findArt">
   <cfargument name="firstRecord" required="true" type="numeric">
   <cfargument name="maxRecords" required="true" type="numeric">

   <cfset var LOCAL = {}>
   <cfset LOCAL.lastRecord = firstRecord + maxRecords>
   <cfquery name="LOCAL.findArt" datasource="#datasource#">
      SELECT *
      FROM art
      WHERE artId >= <cfqueryparam cfsqltype="cf_sql_integer" value="#ARGUMENTS.firstRecord#">
      AND artId < <cfqueryparam cfsqltype="cf_sql_integer" value="#LOCAL.lastRecord#">
   </cfquery>
   <cfreturn LOCAL.findArt>
  </cffunction>
</cfcomponent>

The model should have no knowledge of your view and controller code. The view is used to display values from the model, and the controller changes values in the model.

The controller is in two parts. The first part, Controller.cfc contains functions which modify the model. The second part, controller.cfm handles user input, calls the functions in Controller.cfc, and forwards to the next view.

Controller.cfc

<cfcomponent output="false">

  <cffunction access="public" returntype="Controller" name="init">
    <cfargument required="true" type="Model" name="model">
    <cfset VARIABLES.model = ARGUMENTS.model>
    <cfset REQUEST.model = VARIABLES.model>
    <cfreturn this>
  </cffunction>

  <cffunction access="public" returntype="void" name="nextPage">
    <cfset VARIABLES.model.nextPage()>
    <cfset REQUEST.model = VARIABLES.model>
  </cffunction>

  <cffunction access="public" returntype="void" name="prevPage">
    <cfset VARIABLES.model.prevPage()>
    <cfset REQUEST.model = VARIABLES.model>
  </cffunction>
</cfcomponent>

controller.cfm

<cfsilent>
  <cfif isDefined("FORM.next")>
    <cfset APPLICATION.controller.nextPage()>
    <cflocation url="view.cfm">
  </cfif>

  <cfif isDefined("FORM.prev")>
    <cfset APPLICATION.controller.prevPage()>
    <cflocation url="view.cfm">
  </cfif>
</cfsilent>

Lastly, the view read values from the model and posts user click data to the controller:

view.cfm

<html>
  <body>
    <p>
      <table>
        <cfloop index="artName" list="#REQUEST.model.getArtNames()#">
          <tr>
            <td><cfoutput>#artName#</cfoutput></td>
          </tr>
        </cfloop>
      </table>

      <form action="controller.cfm" method="POST">
        <cfif REQUEST.model.showPrev()>
          <input type="submit" id="prev" name="prev" value="←">
        </cfif>
        <cfif REQUEST.model.showNext()>
          <input type="submit" id="next" name="next" value="→">
        </cfif>
      </form>
    </p>
  </body>
</html>

All that's left is to wire it all together in an Application.cfm

Application.cfm

<cfsilent>
  <cfapplication name="mvc" sessionmanagement="true">

  <cfparam
     name="APPLICATION.controller"
     default="#createObject("component", "Controller").init(
       createObject("component", "Model").init(
         createObject("component", "Data").init()))#">
</cfsilent>

Source Code

  • Coldfusion Eclispe/CFEclispe Project - MVC.zip

Monday 13 July 2009

Coldfusion, JPA & Hibernate

Coldfusion 9 beta has just been released, and one of its major selling points for me is the out of the box Object Relational Mapping support. Excellent!

Now, two things are really annoying me about this:

  1. ORM has been around for a long time, Hibernate for Java and Transfer for Coldfusion have both been around for well over 10 years. Out of the box support for this in Coldfusion is late. Coldfusion is meant to be a 'rapid application development platform', abstracting the database layer means developers can get on with writing more important code, instead of debugging SQL. It should have been here before now.
  2. The CF9 implementation is tied to hibernate. One of the advantages of ORM is that it's database agnostic. If it's a good thing that your code isn't tied to a specific database, why would you want to be tied to a specific persistance provider?

Java Persistence API

Why haven't Adobe used the JPA? That way you can write your database independant code and implement it with whichever JPA implementation suits you best, and seamlessly change to a new implementation if a better one comes along:

If you install Coldfusion on Glassfish or Geronimo you will already have TopLink or OpenJPA available, why would you want to have Hibernate libraries there aswell?

I've had a go with using the JPA and Hibernate from Coldfusion, and it works quite well. I've made use of my Java Utility Class, so you'll need to get up and running first. Or use another way of adding classes and jars to your Coldfusion classpath.

First I created a bunch of JPA annotated entity classes and a persistence.xml to use the CFARTGALLERY database that comes installed with Coldfusion 8.

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="CF_ART_GALLERY_PU" transaction-type="RESOURCE_LOCAL">
 <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <class>OrderStatus</class>
    <class>Media</class>
    <class>Artist</class>
    <class>OrderItem</class>
    <class>Order</class>
    <class>GalleryAdmin</class>
    <class>Art</class>
    <properties>
      <property name="hibernate.connection.driver_class" value="org.apache.derby.jdbc.EmbeddedDriver"/>
      <property name="hibernate.connection.url" value="jdbc:derby:C:\ColdFusion8\db\artgallery"/>
      <property name="hibernate.connection.username" value=""/>
      <property name="hibernate.connection.password" value=""/>
    </properties>
  </persistence-unit>
</persistence>

Art.java

import java.io.Serializable;
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

@Entity
@Table(schema = "APP", name = "ART")
@NamedQueries({
  @NamedQuery(name = "Art.findAll", query = "SELECT a FROM Art a"),
  @NamedQuery(name = "Art.findByArtId", query = "SELECT a FROM Art a WHERE a.artId = :artId"),
  @NamedQuery(name = "Art.findByArtName", query = "SELECT a FROM Art a WHERE a.artName = :artName"),
  @NamedQuery(name = "Art.findByPrice", query = "SELECT a FROM Art a WHERE a.price = :price"),
  @NamedQuery(name = "Art.findByIsSold", query = "SELECT a FROM Art a WHERE a.isSold = :isSold"),
  @NamedQuery(name = "Art.countAll", query = "SELECT COUNT(a) FROM Art a")
})
public class Art implements Serializable {

  private static final long serialVersionUID = 1L;
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ARTID")
  private Integer artId;
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "ARTISTID")
  private Artist artist;
  @Column(name = "ARTNAME")
  private String artName;
  @Column(name = "DESCRIPTION")
  private String description;
  @Column(name = "PRICE")
  private BigDecimal price;
  @Column(name = "LARGEIMAGE")
  private String largeImage;
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "MEDIAID")
  private Media media;
  @Column(name = "ISSOLD")
  private Boolean isSold;

  public Art() {
  }

  public Art(Integer artId) {
    this.artId = artId;
  }

  public Integer getArtId() {
    return artId;
  }

  public void setArtId(Integer artId) {
    this.artId = artId;
  }

  public Artist getArtist() {
    return artist;
  }

  public void setArtist(Artist artist) {
    this.artist = artist;
  }

  public String getArtName() {
    return artName;
  }

  public void setArtName(String artName) {
    this.artName = artName;
  }

  public String getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }

  public BigDecimal getPrice() {
    return price;
  }

  public void setPrice(BigDecimal price) {
    this.price = price;
  }

  public String getLargeImage() {
    return largeImage;
  }

  public void setLargeImage(String largeImage) {
    this.largeImage = largeImage;
  }

  public Media getMedia() {
    return media;
  }

  public void setMedia(Media media) {
    this.media = media;
  }

  public Boolean isIsSold() {
    return isSold;
  }

  public void setIsSold(Boolean isSold) {
    this.isSold = isSold;
  }
}

If you don't want to hand code Java entity classes, a Netbeans wizard can help you generate them and a persistence.xml.

Next I created a CFC to manage the creation of an entity manager, and wrap common functions.

JpaController.cfc

<cfcomponent displayname="JpaController" output="false">
 <cffunction access="public" returntype="JpaController" name="init" output="false">
    <cfscript>
   var LOCAL = {};  
   LOCAL.srcPath = expandPath("java/src");
   LOCAL.destPath = expandPath("java/bin");
   LOCAL.confPath = expandPath("java/conf");
   LOCAL.libPath = expandPath("java/lib");  
   LOCAL.classPath = [LOCAL.libPath, LOCAL.destPath, LOCAL.confPath];
   LOCAL.persistenceUnit = "CF_ART_GALLERY_PU";  
 
   VARIABLES.cfjavautil = createObject("java", "org.adrianwalker.coldfusion.java.util.CfJavaUtil").init(LOCAL.srcPath, LOCAL.destPath, LOCAL.classPath);
     VARIABLES.cfjavautil.compile();    
   
   LOCAL.persistence = VARIABLES.cfjavautil.createObject("javax.persistence.Persistence");
     LOCAL.emf = LOCAL.persistence.createEntityManagerFactory(LOCAL.persistenceUnit);

   VARIABLES.em = LOCAL.emf.createEntityManager();
   return this;
   </cfscript>
 </cffunction>

 <cffunction access="public" returntype="Any" name="createEntity" output="false">
   <cfargument type="String" name="entity" required="true">  
    <cfscript>
      return VARIABLES.cfjavautil.createObject(entity);
   </cfscript>
 </cffunction>

 <cffunction access="public" returntype="void" name="persist" output="false">
   <cfargument type="String" name="entity" required="true">
    <cfscript>
    VARIABLES.em.getTransaction().begin(); 
     VARIABLES.em.persist(entity);
     VARIABLES.em.getTransaction().commit();
   </cfscript>
 </cffunction>

  <cffunction access="public" returntype="Any" name="merge" output="false">
    <cfargument type="String" name="entity" required="true">
    <cfscript>
      VARIABLES.em.getTransaction().begin();    
      mergedEntity = VARIABLES.em.merge(entity);
      VARIABLES.em.getTransaction().commit();
      return mergedEntity;
    </cfscript>
  </cffunction>

  <cffunction access="public" returntype="void" name="remove" output="false">
    <cfargument type="String" name="entity" required="true">
    <cfscript>
      VARIABLES.em.getTransaction().begin();    
      VARIABLES.em.remove(entity);
      VARIABLES.em.getTransaction().commit();
    </cfscript>
  </cffunction>
 <cffunction access="public" returntype="Any" name="createNamedQuery" output="false">  
   <cfargument type="String" name="namedQuery" required="true">
    <cfscript>
    return VARIABLES.em.createNamedQuery("#namedQuery#");
   </cfscript>
 </cffunction> 

 <cffunction access="public" returntype="void" name="close" output="false">
    <cfscript>
    VARIABLES.em.close();
   </cfscript>  
 </cffunction>
</cfcomponent>

I use an Application.cfm to create a single instance of the CFC and store it in application scope.

Application.cfm

<cfapplication name="ColdfusionJPA" sessionmanagement="true">
<cfscript>
  if(NOT isDefined("APPLICATION.jpaController")) {
    APPLICATION.jpaController = createObject("component", "JpaController").init();
  }
</cfscript>

Then finally, a page to use the controller CFC to execute a named query to retrieve a list of Art entities and page through them.

index.cfm

<cfparam name="FORM.firstResult" default="0">
<cfparam name="FORM.maxResult" default="10">

<cfif isDefined("FORM.prev")>
  <cfset FORM.firstResult = FORM.firstResult - FORM.maxResult>
<cfelseif isDefined("FORM.next")>
  <cfset FORM.firstResult = FORM.firstResult + FORM.maxResult>
</cfif>

<cfscript>
  artCount = APPLICATION.jpaController.createNamedQuery("Art.countAll").getSingleResult();
  artList = APPLICATION.jpaController.createNamedQuery("Art.findAll").setFirstResult(FORM.firstResult).setMaxResults(FORM.maxResult).getResultList();
</cfscript>  

<cfoutput>
  <cfloop index="art" array="#artList#">
    #art.getArtId()#) #art.getArtName()#
    <br>
  </cfloop>

  <form action="index.cfm" method="post">
    <cfif FORM.firstResult GT 0>
      <input type="submit" id="prev" name="prev" value="<-">
    </cfif>
    <cfif (FORM.firstResult + FORM.maxResult) LT artCount>
      <input type="submit" id="next" name="next" value="->">
    </cfif>
    <input type="hidden" id="firstResult" name="firstResult" value="#FORM.firstResult#">
    <input type="hidden" id="maxResult" name="maxResult" value="#FORM.maxResult#">
  </form>
</cfoutput>

To have a go with this download the zip below and extract it to your C:\ColdFusion8\wwwroot directory. If you are using my Java Utility Class, then entity classes will be compiled automatically and the JPA and Hibernate JARs will be available from the project lib directory.

Source Code

Why don't you use a LinkedList? Why don't you use a LinkedList? STFU!

I have never seen any real life code where using a linked list has been beneficial. In most cases there is just no need for it and it makes the code run like a dog.

Fine, if your code spends most of it's time inserting and deleting at random points in the list. But seriously come on.

The class below compares ArrayLists and LinkedLists:

package listtest;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

public final class ListTest {

  private static final int TESTS = 3;
  private static final int[] LIST_SIZES = new int[] {1000, 10000, 100000 };
  private static final Random RANDOM = new Random();

  private static long doAdds(final List<Integer> list, final int adds) {
    long startAdds = System.currentTimeMillis();
    for (int i = 0; i < adds; i++) {
      list.add(RANDOM.nextInt(Integer.MAX_VALUE));
    }
    long endAdds = System.currentTimeMillis();

    long time = endAdds - startAdds;

    return time;
  }

  private static long doSequentialGets(final List<Integer> list) {
    long startGets = System.currentTimeMillis();
    int size = list.size();
    for (int i = 0; i < size; i++) {
      int x = list.get(i);
    }
    long endGets = System.currentTimeMillis();

    long time = endGets - startGets;

    return time;
  }

  private static long doRandomGets(final List<Integer> list) {
    long startGets = System.currentTimeMillis();
    int size = list.size();
    for (int i = 0; i < size; i++) {
      int x = list.get(RANDOM.nextInt(size));
    }
    long endGets = System.currentTimeMillis();

    long time = endGets - startGets;

    return time;
  }

  public static void main(String[] args) {

    long[] arrayListAddTimes = new long[TESTS];
    long[] arrayListSequentialGetTimes = new long[TESTS];
    long[] arrayListRandomGetTimes = new long[TESTS];
    long[] linkedListAddTimes = new long[TESTS];
    long[] linkedListSequentialGetTimes = new long[TESTS];
    long[] linkedListRandomGetTimes = new long[TESTS];

    for (int listSize : LIST_SIZES) {
      for (int i = 0; i < TESTS; i++) {
        List<Integer> arrayList = new ArrayList<Integer>();
        List<Integer> linkedList = new LinkedList<Integer>();

        arrayListAddTimes[i] = doAdds(arrayList, listSize);
        linkedListAddTimes[i] = doAdds(linkedList, listSize);

        arrayListSequentialGetTimes[i] = doSequentialGets(arrayList);
        linkedListSequentialGetTimes[i] = doSequentialGets(linkedList);

        arrayListRandomGetTimes[i] = doRandomGets(arrayList);
        linkedListRandomGetTimes[i] = doRandomGets(linkedList);
      }

      System.out.println(String.format("\n----- list size : %s -----\n", listSize));
      System.out.println(String.format("Array list adds mean time: %s ms", mean(arrayListAddTimes)));
      System.out.println(String.format("Linked list adds mean time: %s ms\n", mean(linkedListAddTimes)));
      System.out.println(String.format("Array list sequential gets mean time: %s ms",mean(arrayListSequentialGetTimes)));
      System.out.println(String.format("Linked list sequential gets mean time: %s ms\n",mean(linkedListSequentialGetTimes)));
      System.out.println(String.format("Array list random gets mean time: %s ms",mean(arrayListRandomGetTimes)));
      System.out.println(String.format("Linked list random gets mean time: %s ms\n",mean(linkedListRandomGetTimes)));
    }
  }

  private static long mean(final long[] times) {

    long sum = 0;
    for (long x : times) {
      sum = sum + x;
    }
    int n = times.length;
    return sum / n;
  }
}

The code creates a LinkedList and an ArrayList and inserts random integers. The lists are then read sequentially and then read randomly. This is done for 1000, 10000 and 100000 integer lists, each one repeated 3 times and an average time calculated for: adding, reading sequentially and reading randomly. Check out some results from running this on my machine:

----- list size : 1000 -----

Array list adds mean time: 0 ms
Linked list adds mean time: 0 ms

Array list sequential gets mean time: 0 ms
Linked list sequential gets mean time: 0 ms

Array list random gets mean time: 0 ms
Linked list random gets mean time: 0 ms


----- list size : 10000 -----

Array list adds mean time: 0 ms
Linked list adds mean time: 5 ms

Array list sequential gets mean time: 0 ms
Linked list sequential gets mean time: 119 ms

Array list random gets mean time: 0 ms
Linked list random gets mean time: 109 ms


----- list size : 100000 -----

Array list adds mean time: 21 ms
Linked list adds mean time: 41 ms

Array list sequential gets mean time: 0 ms
Linked list sequential gets mean time: 14125 ms

Array list random gets mean time: 15 ms
Linked list random gets mean time: 15374 ms

Source Code

Saturday 11 July 2009

Dynamic Java Compilation & Class Loading in Coldfusion

Ever wanted to compile java classes from coldfusion? Instantiate classes and load jars not on the classpath at runtime?

Who hasn't?

The java class below uses the Java 6 Compiler API to allow you to compile classes on the fly from coldfusion, and a Classloader to load classes and jars not already on the classpath.

package org.adrianwalker.coldfusion.java.util;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

public final class CfJavaUtil {

  private static final String JAVA_EXTENSION = ".java";
  private static final String JAR_EXTENSION = ".jar";

  private final File srcDir;
  private final File destDir;
  private final File[] classDirs;

  private final String classpath;
  private final URL[] classpaths;

  private final URLClassLoader ucl;

  public CfJavaUtil(final String srcPath, final String destPath,
      final String[] classPaths) throws IOException {

    srcDir = checkPath(srcPath, false);
    destDir = checkPath(destPath, true);
    classDirs = checkPaths(classPaths, false);

    classpath = buildClassPath(classDirs);
    classpaths = buildClassPaths(classDirs);

    ucl = new URLClassLoader(classpaths);
  }

  public void compile() throws IOException, MalformedURLException,
      ClassNotFoundException {
    List<String> options = new ArrayList<String>();
    options.add("-classpath");
    options.add(classpath);
    options.add("-d");
    options.add(destDir.getAbsolutePath());

    List<File> files = findFiles(srcDir, FileDirectoryFilter.JAVA_FILTER);
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

    if (null == compiler) {
      throw new IllegalStateException(
          "Could not create a compiler, make sure you are running in a jdk not a jre");
    }

    StandardJavaFileManager fileManager = compiler.getStandardFileManager(null,
        null, null);
    Iterable<? extends JavaFileObject> compilationUnits = fileManager
        .getJavaFileObjectsFromFiles(files);
    compiler.getTask(null, fileManager, null, options, null, compilationUnits)
        .call();
    fileManager.close();
  }

  public Object createObject(final String className)
      throws MalformedURLException, InstantiationException,
      IllegalAccessException, ClassNotFoundException {

    if (null == className) {
      throw new IllegalArgumentException("Argument 'className' is null");
    }

    return ucl.loadClass(className).newInstance();
  }

  private static String buildClassPath(final File[] classDirs) {

    StringBuilder classPathBuilder = new StringBuilder();
    for (File classDir : classDirs) {
      classPathBuilder.append(classDir.getAbsolutePath());
      List<File> files = findFiles(classDir, FileDirectoryFilter.JAR_FILTER);
      for (File file : files) {
        if (classPathBuilder.length() > 0) {
          classPathBuilder.append(File.pathSeparatorChar);
        }
        classPathBuilder.append(file.getAbsolutePath());
      }
    }
    return classPathBuilder.toString();
  }

  private static URL[] buildClassPaths(final File[] classDirs)
      throws MalformedURLException {
    List<URL> urls = new ArrayList<URL>();
    for (File classDir : classDirs) {
      urls.add(classDir.toURI().toURL());
      List<File> files = findFiles(classDir, FileDirectoryFilter.JAR_FILTER);
      for (File file : files) {
        urls.add(file.toURI().toURL());
      }
    }
    return urls.toArray(new URL[urls.size()]);
  }

  private static File[] checkPaths(final String[] paths, final boolean create)
      throws IOException {
    int pathsLength = paths.length;
    File[] dirs = new File[pathsLength];
    for (int i = 0; i < pathsLength; i++) {
      dirs[i] = checkPath(paths[i], create);
    }

    return dirs;
  }

  private static File checkPath(final String path, final boolean create)
      throws IOException {

    File dir = new File(path);

    if (create) {
      if (dir.exists() && !dir.isDirectory()) {
        throw new IOException(
            "Argument 'dir' exists and is not is not a directory");
      } else if (!dir.exists()) {
        boolean ok = dir.mkdirs();
        if (!ok) {
          throw new IOException("Could not create directory '"
              + dir.getAbsolutePath() + "'");
        }
      }
    } else {
      if (!dir.exists() || !dir.isDirectory()) {
        throw new IllegalArgumentException("Argument 'path' is not a directory");
      }
    }

    return dir;
  }

  private static List<File> findFiles(final File path, final FileFilter filter) {

    List<File> files = new ArrayList<File>();

    File[] fileList = path.listFiles(filter);
    for (File file : fileList) {
      if (file.isDirectory()) {
        files.addAll(findFiles(file, filter));
      } else {
        files.add(file);
      }
    }

    return files;
  }

  private static enum FileDirectoryFilter implements FileFilter {

    JAVA_FILTER(JAVA_EXTENSION), JAR_FILTER(JAR_EXTENSION);

    private final String extension;

    private FileDirectoryFilter(final String extension) {
      this.extension = extension;
    }

    @Override
    public boolean accept(File file) {

      if (file.isDirectory()) {
        return true;
      } else if (file.getName().endsWith(extension)) {
        return true;
      } else {
        return false;
      }
    }
  }
}

Compile the above code into a jar, place it in your C:\ColdFusion8\wwwroot\WEB-INF\lib directory, and restart the server to pic up the jar.

The CF code below shows an example of how the java utility class can be used.

<cfsilent>
  <cfscript>
    srcPath = expandPath("java/src");
    destPath = expandPath("java/bin");
    classPath = [destPath];
    cfjavautil = createObject(
                  "java",
                  "org.adrianwalker.coldfusion.java.util.CfJavaUtil"
                 ).init(srcPath, destPath, classPath);
    cfjavaUtil.compile();
    message = cfjavaUtil.createObject("Message");
  </cfscript>
</cfsilent>

<cfoutput>
  <h1>
    #message.getMessage("Dynamic Java Compilation")#
  </h1>
</cfoutput>

The code creates an instance of the CfJavaUtil class. Uses it to compile some java, then invoke the newly compiled java class.

The project layout for this code has the following structure:

The utility compiles the Message.java source file from the java/src directory to a class file in the java/bin directory. The utility is then called again to create a Message object and call its getMessage method.

The Java and Coldfusion code can be downloaded below. The Java code is built using Apache Maven, or if you can't be bothered to build it yourself, a jar compiled with Java 6 is downloadable below.

To use the Java 6 Compiler API feature you must run the utility in a JDK, not the standard coldfusion JRE. If you see the message below, you must reconfigure your coldfusion server.

To resolve this, point your coldfusion server to a directory containing a Java 6 JDK, and restart.

Source Code

Compiled JAR