Author Archive

Add Spring Security to ColdFusion

January 10, 2011

We are evaluating Spring Security as a part of our authentication and authorization platform. Since we have many ColdFusion apps, it was natural for us to see how it would work in tandem with ColdFusion. Fortunately, since ColdFusion is a Java web application, Spring Security fits right in.

In this post, I’ll show the basics of connecting Spring Security with ColdFusion. I’m not intending to teach how Spring Security works or other details of implementation. For more information, see the Spring Security Reference Documentation (also available as a PDF).

At a high level, you add Spring Security as a Servlet Filter that is setup to run before the ColdFusion servlet in the Java web application config file (web.xml). To make it as easy as possible to see how it works, I’ll show you how to to setup the Spring Security tutorial app. I’m starting with a new install of ColdFusion 9.0.1, installed as a J2EE app under Tomcat 6.0.29 (running on JDK 1.6.0.22, 64-bit on Windows XP Pro).

  1. Download Spring Security from here (using version 3.0.5, the latest as of this writing).
  2. Extract the zip file (spring-security-3.0.5.RELEASE.zip).
  3. Extract the tutorial WAR file located in
    spring-security-3.0.5.RELEASE/dist/spring-security-samples-tutorial-3.0.5.RELEASE.war
    to a local directory (I’ll call it <tutorial-war>).NOTE: If you’ve never run the Spring Security tutorial, you can just put the <tutorial-war> directory into the /webapps directory of your Tomcat app server and run it. Here is more info about Spring Security tutorial.
  4. Copy all of the .jar files from:
    <tutorial-war>/WEB-INF/lib
    into:
    <cf-app>/WEB-INF/lib
  5. Copy the file:
    <tutorial-war>/WEB-INF/classes/log4j.properties
    to:
    <cf-app>/WEB-INF/classes/log4j.properties
  6. Copy the file:
    <tutorial-war>/WEB-INF/applicationContext-security.xml
    to:
    <cf-app>/WEB-INF/applicationContext-security.xml
  7. Copy indicated lines from:
    <tutorial-war>/WEB-INF/web.xml
    to:
    <cf-app>/WEB-INF/web.xml 

    1. Under the line:
      <description>Adobe ColdFusion 9</description>
      (line 5 in my stock CF9 web.xml), insert the <context-param /> blocks from
      <tutorial-war>/WEB-INF/web.xml
      (NOTE: In the code below, I removed the first line in the first <context-param /> from the tutorial that said
      classpath:applicationContext-business.xml.):

      	<!-- INSERTED FOR SPRING SECURITY -->
      
          <!--
            - Location of the XML file that defines the root application context
            - Applied by ContextLoaderListener.
            -->
          <context-param>
              <param-name>contextConfigLocation</param-name>
              <param-value>
      			<!-- REMOVED classpath:applicationContext-business.xml -->
                  /WEB-INF/applicationContext-security.xml
              </param-value>
          </context-param>
      
          <context-param>
              <param-name>log4jConfigLocation</param-name>
              <param-value>/WEB-INF/classes/log4j.properties</param-value>
          </context-param>
      
          <context-param>
              <param-name>webAppRootKey</param-name>
              <param-value>tutorial.root</param-value>
          </context-param>
      
      	<!-- END INSERT FOR SPRING SECURITY -->
      
    2. Under the many <context-param /> blocks and before the <filter /> blocks (around line 64 in my stock CF9 web.xml), insert:
      	<!-- INSERTED FOR SPRING SECURITY -->
      
          <filter>
              <filter-name>springSecurityFilterChain</filter-name>
              <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
          </filter>
      
          <filter-mapping>
            <filter-name>springSecurityFilterChain</filter-name>
            <url-pattern>/*</url-pattern>
          </filter-mapping>
      
      	<!-- END INSERT FOR SPRING SECURITY -->
      
    3. Under the <filter-mapping /> blocks and before the <listener /> blocks (around line 167 in my stock CF9 web.xml), insert:
      	<!-- INSERTED FOR SPRING SECURITY -->
      
          <listener>
              <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
          </listener>
      
          <listener>
            <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
          </listener>
      
          <listener>
              <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
          </listener>
      
      	<!-- END INSERT FOR SPRING SECURITY -->
      
  8. Now, we have to adjust ColdFusion’s logging configuration so that Spring Security’s configuration and libraries are favored.
    1. Rename the file:
      <cfapp>/WEB-INF/cfusion/lib/commons-logging-1.1.1.jar
      to:
      <cfapp>/WEB-INF/cfusion/lib/commons-logging-1.1.1.jar.bak
    2. Rename the file:
      <cfapp>/WEB-INF/cfusion/lib/commons-logging-api-1.1.1.jar
      to:
      <cfapp>/WEB-INF/cfusion/lib/commons-logging-api-1.1.1.jar.bak
    3. Rename the file:
      <cfapp>/WEB-INF/cfform/jars/commons-logging.jar
      to:
      <cfapp>/WEB-INF/cfform/jars/commons-logging.jar.bak
    4. Rename the file:
      <cfapp>/WEB-INF/cfform/jars/commons-logging.properties
      to:
      <cfapp>/WEB-INF/cfform/jars/commons-logging.properties.bak
    5. Create the file:
      <cfapp>/WEB-INF/classes/commons-logging.properties
      , and insert the following into it:

      	org.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.SLF4JLogFactory
      	org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
      
  9. Extract the file security.tld from the META-INF directory of the file
    <cfapp>/WEB-INF/lib/spring-security-taglibs-3.0.5.RELEASE.jar
    and put it into the
    <cfapp>/WEB-INF/lib
  10. Edit the file:
    <cf-app>/WEB-INF/applicationContext-security.xml
    and comment out the <session-management /> block (around line 35), like so:

    	<!--
    	<session-management invalid-session-url="/timeout.jsp">
    		<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
    	</session-management>
    	-->
    
  11. That should be all of the server-side configuration you need to make Spring Security work with ColdFusion. When you startup your app server, you should see a lot of Spring Security related output in your console, ending with:

    Root WebApplicationContext: initialization completed in  ms

    Since you can use JSP Tag Libraries in ColdFusion templates or CFCs, you can import Spring Security’s taglib and then easily restrict access to certain content or code using Spring Security’s access expressions. Here’s an example CFM file.

    <cfimport taglib="/WEB-INF/lib/security.tld" prefix="security">
    <html>
    <head>
    	<title>Spring Security on ColdFusion tester</title>
    </head>
    <body>
    	<h1>Spring Security on ColdFusion Test Application</h1>
    	<cfinclude template="/include/nav.cfm" />
    
    	<security:authorize access="isAuthenticated()">
    	<p>You can see this because you are authenticated.</p>
    	</security:authorize>
    
    	<security:authorize access="hasRole('ROLE_SUPERVISOR')">
    	<p>You can see this because you have supervisor permission.</p>
    	</security:authorize>
    
    </body>
    </html>
    

    That’s just the beginning — but a pretty strong beginning that opens up a whole new world of enterprise-class security.

Advertisements

Investing In ColdFusion

August 18, 2010

If you listen to Dave Ramsey at all, you know he’s all about wise investing. The daveramsey.com development team has made a significant investment in a new RESTful service layer. We’ve invested time in building it so that our site can continue to scale and grow to reach millions more with the message of Financial Peace. And, we’ve invested in the Mach-II framework by writing the code for the new REST Endpoint for version 1.9, so that simple and powerful RESTful services are available to the entire ColdFusion developer community.

Not only that, we’re investing in an upcoming ColdFusion conference, BFusion ’10. I’ll be presenting a session titled “Building a RESTful Service Layer in ColdFusion“. Well over a dozen Dave Ramsey developers will be making the trek from Nashville to beautiful Bloomington, IN for BFusion/BFlex this year. The facilities, speakers, content, and networking opportunities are very valuable. It’s a smaller conference so it’s easy to connect with industry leaders and really dig in to learn some new things. If you’re doing anything with ColdFusion or Flex, or think you might want to get started, this conference is well worth your time.

A Java/Ruby Developer Appreciates ColdFusion?

May 4, 2010

When I moved my family 2,500 miles to join Dave Ramsey’s web team two years ago, I joined because of my passion to help Dave reach more financially distressed people through technology. I didn’t join because his web site used ColdFusion. With more than 15 years of development experience, primarily as a Java, ActionScript, and Ruby developer, I had a relatively low opinion of ColdFusion.

However, as a member of Dave’s web team and the ColdFusion developer community, I’ve seen the truly world-class work that is being done with ColdFusion. I’ve come to appreciate ColdFusion as a worthy peer of other modern development platforms.

What changed my mind? Here are some of the contributing factors:

  • Results. As a very practical person, I observed our very successful website. It serves more than 1M unique visitors, with over 10M page views per month. A company of nearly 300 team members generates a large amount of its income from our site. As a Java developer with my nose in the air, I had never worked on a site that had even close to this much of an impact.
  • Java. ColdFusion applications compile down to Java and run as a standard Java web-app, deployable as a .war file on any Java app server. All of the scalability, flexibility, and power I was used to in my Java web-app development is available in a well-executed ColdFusion app.
  • Mach-II. We have been a Mach-II shop for several years, but we recently rebuilt our primary web site, daveramsey.com, into a 50 module Mach-II 1.8 application. We’ve found Mach-II to be an extremely powerful Model-View-Controller framework that enables us to support our growing number of users. It provides a foundation we can build on that has helped us to roll out new features quickly, without compromising a strong MVC design. (I also had the honor of meeting team Mach-II at BFusion, and found Peter, Kurt, and Matt to be an outstanding group of committed, brilliant developers. We reviewed their work on Mach-II 1.8 while we were developing our new website, and found their code to be among the best ColdFusion code around. They set the bar for outstanding quality and community support.)
  • Libraries. ColdFusion 9 added Hibernate, providing access to the same proven ORM used by leading Java and Groovy apps. The ColdSpring framework provides IoC support on par with Spring. Several automated testing libraries are available; we use MxUnit. RiaForge has 700+ additional open source ColdFusion projects. And, since ColdFusion is Java, thousands more Java libraries are available. We used the Java Topology Suite for a large internal Flex/CF/Java Map mashup application.
  • Productivity. ColdFusion is a dynamic language that runs on a JVM, offering productivity improvements over traditional Java development. From simple things like cffile, cfhttp, cfquery, and cfdump (!), to integration with the frameworks I mentioned above, I’ve found ColdFusion development to be more agile than Struts or Spring MVC.
  • Competition. I’m a huge fan of the Railo and Open BlueDragon efforts, because they provide very healthy competition to Adobe and encourage innovation. They also open CFML up to the open source community, so that developers can deploy ColdFusion applications on a completely open stack. We don’t use them here yet, but may in the future.

ColdFusion isn’t perfect — any experienced developer knows that there is no such thing. Every technology has pluses and minuses. However: real, enterprise-class, powerful, and successful applications are being built on ColdFusion. If you are like I was a couple years ago, and have a low opinion of ColdFusion based on poorly written apps or experience years ago, I’d encourage you to take a second look. You may be pleasantly surprised by what you find.

Omniture Web Services API Code For ColdFusion

December 10, 2009

We’ve been working on a ColdFusion application that uses the Omniture Web Services API. Omniture’s documentation is pretty light in this area, and the examples were not very applicable to ColdFusion. After pulling together several different blogs, docs, and some trial & error, I have created the following incantation. Hopefully it’ll save another Omniture/ColdFusion user a few minutes.

<cfsilent>
    <cffunction name="getOmnitureWsseRequestHeader" access="private" returntype="Any" output="false"
                hint="Return the header needed to make an Omniture web service request as a SOAPHeaderElement object">
        <cfargument name="strUsername" type="String" required="true" />
        <cfargument name="strSecret" type="String" required="true" />
        <cfscript>
            // Unique random number
            var strNonce = createUUID();
            // Date created in Omniture's required format
            var strCreated  = DateFormat(Now(),'YYYY-mm-dd H:mm:ss');
            // Password encoded according to Omniture's requirements
            var strPassword = ToBase64(Hash("#strNonce##strCreated##arguments.strSecret#", 'SHA').toLowerCase());
            // Start to build header
            var objHeader = CreateObject("java", "org.apache.axis.message.SOAPHeaderElement");
            var nodeUserToken = "";
            var nodePassword = "";
            // Construct XML structure with code
            objHeader.init("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "wsse:Security");
            objHeader.setMustUnderstand(1);
            objHeader.setActor("");
            nodeUserToken = objHeader.addChildElement("wsse:UsernameToken");
            nodeUserToken.setAttribute("wsu:Id", "User");
            nodeUserToken.addChildElement("wsse:Username").setValue(arguments.strUsername);
            nodePassword = nodeUserToken.addChildElement("wsse:Password");
            nodePassword.setAttribute("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0##PasswordDigest");
            nodePassword.setValue(strPassword);
            nodeUserToken.addChildElement("wsse:Nonce").setValue(strNonce);
            nodeUserToken.addChildElement("wsu:Created").setValue(strCreated);
        </cfscript>
        <cfreturn objHeader />
    </cffunction>

    <cfscript>
        objHeader = getOmnitureWsseRequestHeader("your-username", "your-secret-code");
        objService = CreateObject("webservice", "your-omniture-wsdl-file-url");
        objService.setHeader(objHeader);
    </cfscript>

</cfsilent>

<cfoutput>
<p>objService.companyGetTokenCount(): #objService.companyGetTokenCount()#</p>
</cfoutput>

To use this test code, do the following:

  1. Copy the code to a .cfm file
  2. Replace “your-username” and “your-secret-code” with your Omniture web service user name & shared secret code
  3. Replace”your-omniture-wsdl-file-url” with the URL to your .wsdl file
  4. Run it — it should work!

Don’t forget to use cfdump on the objService variable to see all of the methods you can call with the API!

The Sweet Java Topology Suite – Part II

March 4, 2009

In a previous post, we described how we started using the Java Topology Suite (JTS) to manipulate postal/zip code polygons that we are viewing in an application built on MapQuest’s Flex API. Since then, we have added the ability to join multiple postal codes into territories. Sometimes over 1,000 postal code polygons will be combined to form a single territory.

We ran into two significant technical hurdles. First, MapQuest’s API doesn’t support polygons with inner holes. So, a donut-shaped polygon would just look like a circle, with no hole in the middle. The other problem was that some of the postal codes were so complicated that the unify process would fail.

Union of postal code polygons with a hole in the middle

Union of postal code polygons with a hole in the middle

This union of postal code polygons should have a hole in the middle

Union of postal code polygons, missing the hole in the middle

If you read the other article, you saw that we did use JTS to simplify polygons (by reducing the number of points that make up the polygon). However, we didn’t end up using those in production because the edges of the simplified polygons would not line up. They end up looking like broken glass, because the simplify process had no regard for adjacent polygon edges.

Simplified polygons with edges that don't line up

Simplified polygons with edges that don't line up

So, we set out on an adventure to simplify the polygons so that the edges of the simplified postal codes matched up. We received some very responsive and helpful guidance from Martin Davis, one of the principle developers of JTS. He also pointed us to the open source tool OpenJUMP, which he also helped to build. Source code from that tool was very helpful as we created our own automated simplification process.

Here’s the simplification process in a nutshell:

  1. Convert the MapQuest postal code polygon data for the current patch (like the lower 48 states) to Well-Known Text (WKT) and save each postal code polygon to an individual file on the file system. For the lower 48 states, this resulted in more than 41,000 files. Here is an unsimplified version of the few polygons we’ll simplify in this example:

    Original, unsimplified postal code polygons

    Original, unsimplified postal code polygons

  2. Read all of the WKT files, one per postal code, and store them as JTS Geometry objects in a collection. To support step six (below), we store the postal code in the geometry object using the very handy Geometry.userData property. That way, each original/source geometry remembers what postal code it represents.
  3. Use JTS to convert the polygons to merged LineString objects. This creates a collection of the outlines of every polygon, where the common polygon edges become a single line.

    Extracted border lines of original polygons

    Extracted border lines of original polygons

  4. Use JTS to simplify the merged LineStrings by reducing the number of coordinates that define each line. Our code iterates across every merged LineString and uses JTS’s DouglasPeuckerSimplifier with a simplify tolerance of 0.01.

    Simplified polygon border lines

    Simplified polygon border lines

  5. Use JTS to create polygons from the simplified LineStrings. The primary JTS class was the magic Polygonizer class, along with code from OpenJUMP that prepared the line data for the Polygonizer.

    New polygons made from simplified lines

    New polygons made from simplified lines

  6. Now the tough part. We have a collection of simplified polygons, but they aren’t linked to any postal codes, so we can’t find the polygon and use it in our application. We needed to match the simplified polygon with the original. Since this is among the most involved processes, I’ll describe it in a bit more detail:
    1. Add each of the original polygons to a JTS SpatialIndex called STRtree. The STRtree provides a quick query interface to find polygons that fall within a spatial constraint.
    2. Iterate through each of the simplified polygons, and:
      1. Query the STRtree to find all of the original polygons that touch the envelope (bounding rectangle) of the current simplified polygon.
      2. Find the polygon in that set which has the smallest distance between its center point and the simplified polygon’s center point.
      3. Once the best matching simplified polygon is found, we copy the postal code from the original Geometry’s userData.
      4. Some simplified polygons have no match in the original set because of holes, so those non-matches are thrown out in this process.
    3. Now that each simplified polygon has been identified as matching a postal code, we write new WKT files for each postal code. Our code that writes these files automatically creates MultiPolygon objects for those postal codes that are made up of more than one polygon.

    Simple polygons that remain after match with originals

    Simple polygons that remain after match with originals

In order to run this process on the lower 48 United States, I had to allocate 7GB of my 8GB of RAM to the JVM so that all 41,000 polygons could be simplified at the same time. Fortunately, it’s worth the time to build. Here are the number of coordinates needed to represent all of the polygons for the three areas, both originally and after simplification, along with the savings realized:

Coordinate Count
Original Simplified Reduction
Lower 48 United States 6,276,000 544,000 12x smaller
Alaska 262,000 15,000 17x smaller
Hawaii 72,000 960 75x smaller

Here’s a larger area of polygons, before and after simplification:

Original postal code polygon sample

Original postal code polygon sample

Simplified postal code polygon sample

Simplified postal code polygon sample

In order to create polygons that maintain any holes in the middle with MapQuest’s polygon API, we used JTS to cut a small slice between any inner features and the exterior of the polygon. This leaves a line in the middle of the polygon, but it’s more acceptable than no hole at all. Hopefully MapQuest will support polygons with inner holes in a later release. In fact, it would be really cool if MapQuest would incorporate other structures and features from JTS, including native WKT support.

Simplified postal codes on map

Simplified postal codes on map

Territory on map with hole enabled by slice

Territory on map with hole enabled by slice

We are very grateful for the Java Topology Suite and the polygon processing it allowed us to complete. The project we’re building for Dave’s Endorsed Local Provider program will be much more successful with these improvements.

Flex, ColdFusion, Java, and BlazeDS: with JSON?

September 22, 2008

We are building a sizable new Flex-based mapping application to support our internal Endorsed Local Provider program. We are passing a significant amount of data between Flex and ColdFusion. So, we architected a service layer that exposes the interface between Flex and CF, and are using BlazeDS to allow us to link Java-based data transfer objects (DTO) on the server side to equivalent Actionscript objects in Flex.

We chose to use Java DTOs rather than CFCs because we found that the Java versions were 15-20 times faster on ColdFusion server. For example, a loop through 1,000 records takes an average of 1.5 seconds:


<cfloop query="rstAddresses">
	<cfset currAddress = CreateObject("component",
                                "components.lampo.mapping.vo.Address") />
	<cfset currAddress.setId(rstAddresses.address_id) />
	<cfset currAddress.setStreet1(rstAddresses.address_line_1) />
	<cfset currAddress.setStreet2(rstAddresses.address_line_2) />
	<cfset currAddress.setCity(rstAddresses.city) />
	<cfset currAddress.setStateCode(rstAddresses.state) />
	<cfset currAddress.setPostalCode(rstAddresses.zipcode) />
	<cfset stcAddresses[rstAddresses.address_id] = currAddress />
</cfloop>

Changing the first line to use a Java object reduces the time to an average of 75 milliseconds:


<cfloop query="rstAddresses">
	<cfset currAddress =
                CreateObject("java", "com.lampo.mapping.vo.Address").init() />
    ...
</cfloop>

I created a custom Java class that generates the Actionscript DTOs from the Java DTOs. We had no trouble sending Java objects to Flex with ColdFusion services through BlazeDS where the parameters passed from Flex were built-in objects, like Strings, Numbers, or Maps. However, when calling a method from Flex that passed one of our custom DTOs as a parameter, the server reported the following error: “Unable to invoke CFC – Could not find the ColdFusion Component or Interface”.

It turns out that if you use ColdFusion services through BlazeDS, meaning that you are using the “cf-object” adapter in your remoting-config.xml, BlazeDS only links ActionScript DTOs with ColdFusion CFC objects on the server. You have to use the “java-object” adapter for it to link Actionscript DTOs to their Java counterparts, but then you have to build your entire service layer in Java, instead of ColdFusion.

Here’s an example of the kind of objects we’re talking about. We have a service method called getPolygons(BoundingBox bb). Flex passes a BoundingBox object, which contains two LatLng objects, and the server returns all of the zip code polygons in that box.

The Java BoundingBox DTO looks like this:


package com.lampo.mapping.vo;
import java.io.Serializable;

public class BoundingBox implements Serializable
{
	//Properties
	private LatLngOnly upperLeftLatLng;
	private LatLngOnly lowerRightLatLng;

	public BoundingBox() {;}

	//Methods
	public LatLngOnly getUpperLeftLatLng() {
		return upperLeftLatLng;
	}
	public void setUpperLeftLatLng(LatLngOnly upperLeftLatLng) {
		this.upperLeftLatLng = upperLeftLatLng;
	}
	public LatLngOnly getLowerRightLatLng() {
		return lowerRightLatLng;
	}
	public void setLowerRightLatLng(LatLngOnly lowerRightLatLng) {
		this.lowerRightLatLng = lowerRightLatLng;
	}

}

And the auto-generated Actionscript version is:


package com.lampo.mapping.vo
{
    import com.lampo.mapping.vo.LatLngOnly;

    [RemoteClass(alias="com.lampo.mapping.vo.BoundingBox")]
    public class BoundingBox
    {
        public var lowerRightLatLng:LatLngOnly;
        public var upperLeftLatLng:LatLngOnly;

        public function getLowerRightLatLng():LatLngOnly
        {
            return this.lowerRightLatLng;
        }
        public function setLowerRightLatLng(in_lowerRightLatLng:LatLngOnly):void
        {
            this.lowerRightLatLng = in_lowerRightLatLng;
        }
        public function getUpperLeftLatLng():LatLngOnly
        {
            return this.upperLeftLatLng;
        }
        public function setUpperLeftLatLng(in_upperLeftLatLng:LatLngOnly):void
        {
            this.upperLeftLatLng = in_upperLeftLatLng;
        }
    }
}

The key to BlazeDS’s ability to link the Actionscript to the Java object is the [RemoteClass(alias="...")] block. From ColdFusion to Flex, a BoundingBox or any other custom object is automatically serialized by BlazeDS and all is well. The problem is going from Flex back to CF with Java DTOs.

To address this, we tried doing some enhancements to the built-in deserialization code, wrapping a ColdFusion DTO around a Java DTO, and decompiling coldfusion.flash.messaging.ColdFusionAdapter to figure out how to make it load Java DTOs. After weighing these ideas we decided to try serializing our Actionscript DTOs into JSON before sending them to ColdFusion. This turned out to be a relatively pain-free solution that maintained our desire to use matching objects on the client and server.

Details

First, we found Adobe’s as3corelib, which includes very handy methods for serializing any object into JSON. Once you download their libraries and include them in your Flex project, it’s as simple as:


import com.adobe.serialization.json.JSON;
JSON.encode(yourObj);

To decode them, we found JSON-lib on SourceForge. Once you have it installed in ColdFusion (see below) you can add a function like this in your Java DTO:


public static BoundingBox makeFromJSON(String inJSON)
	throws Exception
{
	// Place property name/classes in this
        // map to give JSONFunction hints about
	// how to deserialize the JSON
	Map<String, Class> hintsMap = new HashMap<String, Class>();
	hintsMap.put("upperLeftLatLng", LatLngOnly.class);
	hintsMap.put("lowerRightLatLng", LatLngOnly.class);
	BoundingBox bb = (BoundingBox)JSONObject.toBean(
				JSONObject.fromObject(inJSON), BoundingBox.class, hintsMap);
	return bb;
}

Since the BoundingBox object includes properties that are also custom objects, you have to provide a Map that links the property names to the classes, so that JSON-lib can generate the object from the input JSON. This is very handy, and can be called in ColdFusion like this:


<cfscript>
	bbStatic = CreateObject("java", "com.lampo.mapping.vo.BoundingBox");
	jsonBB = "{""upperLeftLatLng"":{""longitude"":-84.5,""latitude"":36}," &
             """lowerRightLatLng"":{""longitude"":-86.79118,""latitude"":39.107}}}";
	realBB = bbStatic.makeFromJSON(jsonBB);
</cfscript>

The JSON-lib setup is a little tricky. Here’s how I installed it into ColdFusion 8:

  1. Download JSON-lib and copy the .jar to WEB-INF/lib. The .jar I used is: json-lib-2.2.2-jdk15.jar.
  2. Download EZ-Morph and copy the .jar to WEB-INF/lib. The .jar I used is: ezmorph-1.0.5.jar.
  3. Download Apache’s Commons-Lang library and copy the .jar to WEB-INF/lib. The .jar I used is: commons-lang-2.4.jar.
  4. Download Apache’s Commons-Collections library and copy the .jar to WEB-INF/lib. The .jar I used is: commons-collections-3.2.1.jar.
  5. Copy the following JARs from WEB-INF/cfusion/lib to WEB-INF/lib: commons-logging.1.0.4.jar, commons-logging-api.1.0.4.jar, log4j-1.2.12.jar.

With this solution, we’re seeing excellent performance and are maintaining the goal of passing Strongly-typed DTOs between Flex & ColdFusion through BlazeDS using a well-defined service interface.

Java Topology Suite — Sweet!

August 26, 2008

We have been building a sophisticated mapping application in Adobe Flex that enables our internal advisors for Endorsed Local Providers (ELPs) to visualize the regions that our partners cover throughout the country. We’re using MapQuest’s Enterprise API, which has a pretty robust ActionScript 3 library along with a nice Java library.

We wanted to be able to display many zip code polygons on the map, and enable our advisors to click on a zip code and see the ELPs who cover that zip code, among other things. However, early in development, we found that the performance of the application suffered greatly when displaying a large number of zip code polygons.

With focused intensity, we took several steps to improve the performance of the map. These steps include:

  • Reduce the complexity of the zip code polygons.
  • Combine zip codes with multiple polygons into a single polygon.
  • Cache the resulting polygon definitions in our local database, as gzipped XML.

After several failed attempts to accomplish these tasks, we found the Java Topology Suite (JTS), an open source collection of geospacial data manipulation tools created by Vivid Solutions of Victoria, Canada. While capable of many other things, we used JTS to unify multiple polygons into a single polygon, and to reduce their complexity.

Here’s our process to unify and simplify polygons from MapQuest’s API through JTS:

  1. Extract the polygons from a MapQuest FeatureCollection to Well-Known Text (WKT) format.
  2. Create a JTS Geometry object for each polygon.
  3. Use JTS to create a GeometryCollection, then union the polygons with the Geometry.buffer() function.
  4. Simply the resulting polygon with JTS’s TopologyPreservingSimplifier.simplify() function.

Here is the most important portion of the code that does all of the above:

/**
 * Unifies and simplifies a MapQuest FeatureCollection using JTS.
 */
public synchronized FeatureCollection unifyFeatureCollection(
    FeatureCollection origFc, double simplifyTolerance) throws Exception
{
    // Extract polygons from FeatureCollection and convert to Well-Known Text
    ArrayList polygonsAsWTK = featureCollectionToWTK(origFc);

    // Create JTS Geometries
    WKTReader rdr = new WKTReader();
    Geometry[] geom = new Geometry[polygonsAsWTK.size()];
    for (int i = 0; i  0) {
        unifiedPolygon = TopologyPreservingSimplifier.simplify(
                                    unifiedPolygon, simplifyTolerance);
    }
    // Convert result back to FeatureCollection and return
    return jtsGeometryToFeatureCollection(origFc, unifiedPolygon);
}

/**
 * Called by unifyFeatureCollection(), returns
 * the union of the input Geometry array as a single
 * JTS Geometry.
 */
private Geometry unifyPolygonArray(Geometry[] geom)
{
    GeometryFactory fact = geom[0].getFactory();
    Geometry geomColl = fact.createGeometryCollection(geom);
    Geometry union = geomColl.buffer(0.0);
    return union;
}

/**
 * Changes a JTS Geometry into a MapQuest FeatureCollection
 */
private FeatureCollection jtsGeometryToFeatureCollection(
    FeatureCollection origFc, Geometry unifiedPolygon)
{
    FeatureCollection newFc  = new FeatureCollection();
    List polygons  = PolygonExtracter.getPolygons(unifiedPolygon);
    for (Geometry currPolygon : polygons)
    {
        // Parse: POLYGON ((-123.862898 46, -123.863671 46.000707, ....)
        String polygonAsWKT = currPolygon.toText();
        String [] wktPoints = polygonAsWKT.substring(
                                (polygonAsWKT.indexOf("((")+2),
                                 polygonAsWKT.indexOf(")")).split(", ");
        ArrayList newLatLngList = wktPointsToLatLngList(wktPoints);
        PolygonFeature newPf = makePolygonFeatureFromPoints(origFc, newLatLngList);
        newFc.add(newPf);
    }
    return newFc;
}

The result: it works really well. There is still room for performance improvement, but the gains we observed were extremely significant

If you’re working with mapping applications, we’d love to hear from you.  Leave us a comment and tell us about it.

Simple SOA with Ajax & ColdFusion Kickoff

July 2, 2008

We are building a small new tool for our Endorsed Local Provider (ELP) advisors using Ajax techniques on the client with a service-oriented backend. One developer will build the client-side with Prototype classes, and another will build the backend on our existing ColdFusion application servers. We’ll use CFJSON to easily package data for both environments.

We just designed the service methods that will be used by the client and implemented on the server side. We’ll deploy stub versions of all of the service methods that so that the client and server code can be developed simultaneously. I’m excited because this will enable us to build a great tool in a quick and efficient way, and enable our advisors to help link up more people with the advice they need.