Wednesday, April 16, 2008

About me updated

I realized that my "About Me" link article hadn't been updated in quite a while, so I added some recent information and filled in my resume (such as it is) for the time before I worked at Allaire/Macromedia/Adobe. It was hard for me to remember (I've been here for 11 years last month!) but I found out you can download DCE as open source, and that there isn't really much information about OSF/1 out on the web. This is a real shame since I still am very proud of what I was part of waaaay back in 1990. :-)

Enjoy.

Thursday, April 10, 2008

Reprint: Consuming Web Service complex types in ColdFusion

In previous posts I have referenced an excellent article written by Doug James and Larry Afrin from the University of South Carolina. The link for that article has since gone dead, but someone helpfully posted a link to the article via a web archive. I am going to reproduce it here so I have an easy reference for it and so its close to my earlier post on array types.


Notes on Interfacing ColdFusion MX to External Web Services Requiring Complex-within-Complex XML Documents as Input


Authors:
Doug James (jamesd@musc.edu)
Larry Afrin, MD (afrinl@musc.edu)
Hollings Cancer Center

Medical University of South Carolina
March 31, 2005

<cfacknowledgement>
The authors would like to acknowledge Macromedia's Tom Jordahl (who we understand is sort
of the principal developer and "guru" of ColdFusion's web services functionality) both for his
assistance in helping them understand how ColdFusion handles certain complex web service
interactions and for his critical review of the following document.
</cfacknowledgement>

<cfdisclaimer>
While the authors have tried to ensure the accuracy and utility of the information below, they
offer the following information with no warranties whatsoever and hereby explicitly state that their employer has had absolutely nothing to do with the development of this document and
therefore also bears no liabilities with regard to how the information presented here may be used.

The authors are also quite sure that despite Tom Jordahl's review of this document, Macromedia
takes no responsibility for this information, either. By their posting of this information, the
authors are not offering themselves as support resources for other developers wrestling with the
problems addressed herein. Requests for assistance in this area that are communicated to the
authors may or may not be acknowledged or answered, solely at the authors' whim. After all, we
do have to attend first to our day jobs (which often spill over into our night jobs, too.) ;-)
</cfdisclaimer>

=======================================================

CFMX is able to communicate with web services hosted in arbitrary computing environments as long as the SOAP standards are followed. The SOAP standards require that all input arguments to a web service be passed as XML documents. The expected format of the XML document for any given input argument is defined either directly in the service's WSDL (Web Services Description Language) document, or by reference in that WSDL document to another data element definition document.

Before proceeding further to discuss how CFMX handles "complex"-type web service input arguments, it is helpful to review how CFMX handles a <cfinvoke> (or equivalent) request in general. CFMX first retrieves the target service's WSDL, then runs this WSDL through the WSDL2Java tool (see below for more information), which outputs Java code defining Java-based classes equivalent to the XML data structures defined in the WSDL. This Java code is then compiled. The input arguments provided to <cfinvoke> are then mapped to the Java types generated by the compilation, and finally the Java stub functions for those types are called. In this fashion, CFMX *automatically* converts input values referenced in <cfinvoke> or
<cfinvokeargument> tags to XML documents of the appropriate formats based on input argument format information provided in the service's WSDL. The burden on the coder, of
course, is to ensure the input values are structured in accordance with what CFMX expects to
find based on the translation by WSDL2Java from an XML-based data structure to a Java-based
data structure.

A similar process is followed to handle the service's return value and other output arguments.

This process is quite straightforward for "simple" type input arguments, such as simple strings or numeric values. Because ColdFusion variables, strictly speaking, are untyped, CFMX
automatically converts simple input ColdFusion variables to the equivalent string-based simple
XML structures.

However, some web service input arguments are XML documents of "complex" type. As briefly
described in the ColdFusion MX 7 documentation at: http://livedocs.macromedia.com/coldfusion/7/htmldocs/00001554.htm for complex-type arguments, CFMX expects to find a ColdFusion structure provided as the value for the input argument. Unfortunately, the ColdFusion MX documentation only provides clear examples of how to compose a ColdFusion structure that corresponds merely to a relatively simple form of a "complex" XML document in which the child elements of an outer "complex" parent element are simple scalar values, as illustrated in Example 1:

---------Example 1:------------------------------------
WSDL snippet:

<s:complexType name="Employee">
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" name="fname" type="s:string" />
<s:element minOccurs="1" maxOccurs="1" name="lname" type="s:string" />
<s:element minOccurs="1" maxOccurs="1" name="age" type="s:int" />
</s:sequence>
</s:complexType>

Sample XML document:

<Employee>
<fname>John</fname>
<lname>Smith</fname>

<age>25</age>
</Employee>
CFML snippet:
<!--- Create a structure using CFScript, then call the web service. --->
<cfscript>
stUser = structNew();
stUser.fname = "John";
stUser.lname = "Smith";
stUser.age = 23;
ws = createObject("webservice", "http://somehost/echosimple.asmx?wsdl");
ws.echoStruct(stUser);

</cfscript>
-------------------------------------------------------

While the above is helpful for "simple" complex-type input arguments, the ColdFusion MX
documentation provides no examples of how to compose a ColdFusion structure that corresponds to a more complex form of a complex-type XML document in which the child elements of an outer "complex" parent element are themselves "complex" parent elements (so-called "complex-within-complex" XML documents), as illustrated in Example 2:

---------Example 2:------------------------------------

WSDL snippet:

<s:complexType name="Employee">
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" name="fname" type="s:string" />
<s:element minOccurs="1" maxOccurs="1" name="lname" type="s:string" />
<s:element minOccurs="0" maxOccurs="unbounded" name="nickname" type="s:nickname" />
<s:element minOccurs="1" maxOccurs="1" name="age" type="s:int" />
<s:element ref="s:address" minOccurs="0" maxOccurs="unbounded" name="address" />
</s:sequence>
<xsd:attribute name="employeeGender" type="string" use="required"/>
</s:complexType>

<s:complexType name="nickname">
<s:simpleContent>
<s:extension base="string" />
</s:simpleContent>
</s:complexType>

<s:complexType name="address">
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" name="street" type="s:string" />
<s:element minOccurs="1" maxOccurs="1" name="street" type="s:string" />
<s:element minOccurs="1" maxOccurs="1" name="state" type="s:state" />
<s:element minOccurs="1" maxOccurs="1" name="zip" type="s:zip" />
</s:sequence>
<xsd:attribute name="addressType" type="string" use="required"/>
</s:complexType>
Sample XML document:
<Employee employeeGender="Male">
<fname>John</fname>
<lname>Smith</fname>
<nickname>Jack</nickname>
<nickname>Johnny</nickname>
<age>25</age>
<address addressType="Home">
<street>25 Main Street</street>
<city>Townville</street>
<state>Anystate</state>
<zip>99999</zip>
</address>
</Employee>
CFML snippet:
<!--- Create a structure using CFScript, then call the web service. --->
<cfscript>
stUser = structNew();
stUser.fname = "John";
stUser.lname = "Smith";
stUser.age = 23;
<!--- If .x corresponds to *element* <x>, then what syntax is used to specify an *attribute*?
How should employeeGender get set up in this struct? Read on to find out. --->
<!--- And what about the *two* nicknames? stUser.nickname = ... clearly won't work.
Read on to find out how this is handled. --->
<!--- And what about <address>? Does that get coded simply as stUser.address = structNew(),
stUser.address.street = "25 Main Street", etc. etc.????? The answer is "No."
Read on to find out more. --->
ws = createObject("webservice", "http://somehost/echosimple.asmx?wsdl");
ws.echoStruct(stUser);
</cfscript>
-------------------------------------------------------
There are just a few key (pardon the pun) principles you need to understand in order to determine how to compose a CF structure that CFMX will map to the properly formatted XML document needed as an input argument to an arbitrary web service:

(1) Principle #1: Any given key in a CF structure will be mapped by CFMX into *either* an
element name *or* an attribute name depending on what role the WSDL says that particular
name should play at that level in the document. In other words, you do not need to differentiate
between attributes and elements in the CFML structure you create; ColdFusion will automatically
handle for you the proper mapping of each key into either an attribute or element as required by
the WSDL.

Following along with Example 2 above, then, stUser.age will get translated into an <age> child *element* within the <Employee> parent element, and stUser.employeeGender will get translated as the employeeGender *attribute* within the <Employee> element.

(2) Elements in a WSDL that can occur more than once (cf. the "address" element in Example 2 above) are represented in the CF struct as an array. OK, but an array of what? Well, it depends on how the WSDL defines the subelements.

Again, following along with Example 2 above, <address> would be coded into the stUser
structure as follows:

  stUser.address = arrayNew(1);
stUser.address[1] = structNew();
stUser.address[1].street = "25 Main Street";
stUser.address[1].city = "Townville";
stUser.address[1].state = "Anystate";
stUser.address[1].zip = "99999";
stUser.address[1].addressType = "Home";
and the nicknames would be coded as follows:
  stUser.nickname = arrayNew(1);
stUser.nickname[1].value = "Jack";
stUser.nickname[2].value = "Johnny";

Hey! Where did ".value" come from? Read on:

(3) As the complexity of the format of the required input XML document increases, it may
become increasingly difficult to "guess," using the above two principles, how the corresponding CF structure should be coded. (Similarly, when working with web service return variables, or
output arguments, of complex-within-complex type, it may become difficult understanding why
CFMX has translated the output into the rather complex structure revealed by <cfdump>.) To help work through this, the coder should apply the WSDL2Java tool (see below for more
information) against the target service's WSDL and carefully examine the output of this tool.
WSDL2Java is an open source utility that takes a WSDL as input and outputs the Java beans
corresponding to the methods of the web service described by the provided WSDL; importantly,
the inputs and outputs for these methods are also defined in the corresponding beans. Upon
careful examination, the WSDL2Java output -- i.e., the Java code defining each bean -- clearly
identifies the expected layouts of the CF structures corresponding to the inputs and outputs of the service's various methods.

For example, one of the things that becomes clear upon examining the WSDL2Java output has to
do with the use of ".value" above. This is complicated, so read carefully: If an element (or
sub-element) of an input argument defined in the WSDL is declared in the WSDL to be of
"complex" type, but the corresponding definition in the WSDL of that complex datatype declares
that the value of that datatype is in fact only a simple scalar value, then CFMX assumes the value to be passed as input for that element (i.e., the value placed between the <x> and </x> tags) will be found in the corresponding CF structure (at the appropriate level) in association with a key named "value".

Below is another example of a web service input argument formatting problem that was solved by examining the output of the WSDL2Java tool. This is a real-world example of using CFMX to interface to a UDDI server. UDDI stands for Universal Data Discovery & Integration. UDDI servers are sort of the Domain Name System (DNS) of the web services world. UDDI servers store information about web services, including their addresses. A given web service could have many implementations around the world. An application that wants to make use of these implementations doesn't have to know their addresses as long as (1) the service and its implementations are registered in UDDI, and (2) the application knows the address of at least one UDDI server (part of the UDDI standard includes, a la the Network News Transport Protocol (NNTP, used by Usenet discussion groups), a replication protocol so that UDDI servers exchange database updates with each other in order for them all to stay in sync). UDDI servers can have a number of programmatic interfaces (e.g., URL-parameterized HTTP GETs), but the most robust interface they support is a web service interface. That's right: you can use web services to query UDDI servers about web services. However, the WSDL for the web service one uses to query a UDDI server is quite complex. More information about UDDI can be found at http://uddi.org.

Here's an example of using the "find_business" method defined in the UDDI WSDL to retrieve information from the target UDDI server about the first 50 businesses known to that server whose names begin with "A":

---------Example 3:------------------------------------

WSDL snippet:
<xsd:complexType name="find_business">
<xsd:sequence>
<xsd:element ref="uddi:name" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="uddi:identifierBag" minOccurs="0"/>
<xsd:element ref="uddi:categoryBag" minOccurs="0"/>
<xsd:element ref="uddi:tModelBag" minOccurs="0"/>
<xsd:element ref="uddi:discoveryURLs" minOccurs="0"/>
<xsd:element ref="uddi:findQualifiers" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="generic" type="string" use="required"/>
<xsd:attribute name="maxRows" type="int" use="optional"/>
</xsd:complexType>

<xsd:complexType name="name">
<xsd:simpleContent>
<xsd:extension base="string">
<xsd:attribute ref="xml:lang" use="optional"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
CFML snippet:
<cfscript>
// define the main outer structure, findBiz in this case, can be named anything one chooses
findBiz = structNew();

//generic and maxRows are attributes (required and optional, respectively) of the uddi:find_business tag
findBiz.generic = "2.0";
findBiz.maxRows = 50;

//uddi:find_business tag accepts one *or more* "name" values, so an array is used
findBiz.name = arrayNew(1);
findBiz.name[1] = structNew();

//uddi:name tag is a complex type with a string value, so 'value' is used as the key to the structure
findBiz.name[1].value = "A";
</cfscript>

<!---
Important note:
In the <cfinvoke> below, the URL to the UDDI WSDL is the value of the "webservice"
parameter; the "method" parameter specifies the UDDI inquiry method called "find_business",
which returns a uddi:businessList object in the returnVariable "busList".

In the UDDI WSDL, the "find_business" method is defined as requiring an input parameter
named "body" of complex type "find_business" which is also defined in the WSDL. The
parameter name "body" shown in the <cfinvoke> below could also have been set up as a
<cfinvokeargument name="body" value="#findBiz#"> tag within the <cfinvoke>.

The actual version 2.0 UDDI WSDL is at http://uddi.org/wsdl/inquire_v2.wsdl. This address
cannot be referenced as the "webservice" parameter in the <cfinvoke> tag because this particular
WSDL does not define the addresses of any actual UDDI servers. Thus, to query a UDDI server,
one has to copy this "generic" WSDL to somewhere else and modify it by adding in the proper
WSDL code to identify at least one actual UDDI server. The <cfinvoke> is then pointed at this
modified WSDL. CFMX will read the modified WSDL from this location, and in this modified
WSDL CFMX will find where it has to go to contact the actual UDDI service being targeted by
the coder.

The WSDL code that identifies an actual UDDI server is as follows. This happens to be the
real code for pointing at the main IBM UDDI server. This code gets inserted just ahead of the
closing </definitions> tag of the "generic" UDDI WSDL.

<service name="InquireSoap">
<port name="InquireSoap" binding="tns:InquireSoap">
<soap:address location="http://uddi.ibm.com/ubr/inquiryapi" />
</port>
</service>
--->

<cfinvoke
webservice = "http://www.webhost.com/modified_uddi_v2.wsdl"
method = "find_business"
body = #findBiz#
returnvariable="busList">
</cfinvoke>
-------------------------------------------------------


And another example from the world of UDDI, this time using the get_businessDetail method:

---------Example 4:------------------------------------

WSDL snippet:
<xsd:complexType name="get_businessDetail">
<xsd:sequence>
<xsd:element ref="uddi:businessKey" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="generic" type="string" use="required"/>
</xsd:complexType>

<xsd:simpleType name="businessKey">
<xsd:restriction base="string"/>
</xsd:simpleType>
CFML snippet:
<cfscript>
// busDetail is outer main structure
busDetail = structNew();

// generic is the required attribute of the "get_businessDetail" tag
busDetail.generic = "2.0";

//businessKey tag can occur many times, so it is mapped to an array
busDetail.businessKey = arrayNew(1);

/*
Because the "get_businessDetail" tag has attributes and sub-elements, the sub-elements have to
be mapped into structures as well, and, similar to the uddi:name above, the sub-element
"businessKey" just contains a string, so the key name 'value' is used. */

for (x = 1; x LTE arrayLen(busKeys); x = x + 1) {
busDetail.businessKey[x] = structNew();
busDetail.businessKey[x].value = busKeys[x]; //busKeys is an array predefined from another process
}
</cfscript>

<!---
As in Example 3, the UDDI WSDL declares that an input parameter named "body", of
complex type "get_businessDetail", is required for the "get_businessDetail" method.
--->
<cfinvoke
webservice = "http://www.webhost.com/modified_uddi_v2.wsdl"
method = "get_businessDetail"
body = #busDetail#
returnvariable="busEntity">
</cfinvoke>
-------------------------------------------------------

MORE INFORMATION ABOUT WSDL2Java:

WSDL2Java is a Java program that was created by the Apache group and is included in the CFMX distribution with the Axis package, the Apache group's implementation of the W3C SOAP standard.

To run WSDL2Java from the command line:

  1. The current directory needs to be the ColdFusion installation's 'lib' directory:
    • Windows: C:\CFusionMX\lib
    • RedHat Linux: /opt/coldfusionmx/lib

  2. Set classpath to include the following jar files:
    • RedHat Linux:
      • axis.jar
      • saaj.jar
      • jaxrpc.jar
      • xercesImpl.jar
      • wsdl4j.jar
      • commons-logging-1.0.2.jar
      • commons-discovery.jar
      • xml-apis.jar
      • activation.jar (This jar file can be found by adding runtime/lib/jrun.jar to the end of the classpath. Alternatively, it can be downloaded from Sun's Java Activation Framework web site.)

    • Windows:
      • SET
        CLASSPATH=axis.jar;saaj.jar;jaxrpc.jar;xercesImpl.jar;wsdl4j.jar;commons-logging-1.0.2.jar;commons-discovery.jar;activation.jar;xml-apis.jar

  3. Run the WSDL2Java program:
       Usage:  java org.apache.axis.wsdl.WSDL2Java [options] WSDL-URI
    '-v' option prints informational messages
    '-o' option is the output directory for emitted files.
    WSDL-URI can be either local or remote


    Example: java org.apache.axis.wsdl.WSDL2Java -v -o C:\wsdl2java_output http://uddi.org/wsdl/inquire_v2.wsdl

  4. More information about WSDL2Java can be found on the Apache Web Service web site, currently http://ws.apache.org/axis/java/user-guide.html#WSDL2JavaBuildingStubsSkeletonsAndDataTypesFromWSDL.


LiveCycle Data Services 2.6 Public Beta

We are close to releasing LiveCycle Data Services version 2.6, which is the technology that allows you to do some really cool stuff with Flex 3 data driven applications. This release is based on the BlazeDS 3.0 open source technology, which includes RPC and Messaging technologies for Flex, but in addition gives you better scalability by including RTMP based channels, a new scalable HTTP based channel technology and some very powerful abilities to manage datasets, including automatic synchronization of changes, conflict detection, paging and support for Flex and AIR offline data caching.

In preparation for the final release, we released a public beta (we call it beta 2) on Adobe Labs last night.

Check out details at http://labs.adobe.com/technologies/livecycle_dataservices2_6/.

Tuesday, April 08, 2008

Array types in ColdFusion web services

I get asked questions about publishing web services in ColdFusion often and this is one that many folks run in to that I wanted to post about as I just got asked this. Usually people send mail to Ben Forta, and he just forwards them on to me. :-)

Question - How do I create a function that has an array of custom made objects as an argument or return value?

First, let me recommend reading the ColdFusion 8 Developers guide chapter on web services (http://livedocs.adobe.com/coldfusion/8/htmldocs/webservices_01.html). I would also recommend reading Ben Forta's (et al) book, the Web Application Construction Kit Volume 3, chapter 68 – Creating and Consuming Web Services.

The first piece of information to know is that the CFML complex types, such as they are, might not the best things to use when creating a Web Service. Lets take Struct's for example. When you define an argument to a function as a struct, the XML Schema that is emitted for the WSDL defines an object that has the "any" type as both the key and the value. But this doesn't give the consumer of the web service much information - are there strings or complex types as the key? What kind of values should there be? Should there be various different things contained in the structure?

A better way to create this service is by defining exactly what kind of things you expect or return. To do this you would create a ColdFusion Component (CFC) that used the (mostly useless except for web services) cfproperty tag to describe the structure. Lets say our structure contained a just string pairs. Here is how you would define that:

<cfcomponent>
<cfproperty name="key" type="string">
<cfproperty name="value" type="string">
</cfcomponent>

Now our XML Schema in the WSDL would define a complexType that has two element in it at key (of type string) and value (also of type string). This has the advantage of clarity and also is potnetially much more interoperabile.

But the original question was about arrays. Lets get back to that.

If you want to publish a web service using CFC's then you would define the cffunction that takes an array as an argument or as a returnType. You can define this array to be of a particular CFC type that you have defined using the cfproperty tag as we did above. The example in the Forta book (Volume 3, page 298, listing 68.11 and 68.12) also shows this. The missing piece is how to specify that the argument is an array.

<cffunction name="GetCreditRating" returntype="string" output="no" access="remote">
<cfargument name="person" type="CreditPerson[]" required="yes">

Notice the "[]" after the name of the component. This will indicate that the WSDL should define the CreditPerson complexType and that the argument to the function should be 1 or more (MaxOccurs="unbounded") of these complex type elements i.e. an array. You can use this with types defined by CFCs or even for simple types (e.g. string[]). This syntax is valid in both the returnType attribute and the type attribute of cfargument.

This information is in the ColdFusion 8 documentation at
http://livedocs.adobe.com/coldfusion/8/htmldocs/webservices_20.html

The follow up to this is if you want to consume a web service (such as the one above) you would define an array in CFML and put in it Structs that correspond to the complex type in the WSDL. Here is an example again based on the GetCreditRating that uses the new CF8 syntax for creating stuctures:

<cfscript>
arg = ArrayNew(1);
s1 = { FirstName=Tom, Lastname=Jordahl, …};
s2 = { FirstName=Ben, Lastname=Forta, …};

arg[1] = s1;
arg[2] = s2;
</cfscript>

Then you would pass the “arg” array as the parameter to the invocation of the web service.

I hope this fleshes out a little but about array types for ColdFusion web services.

Another Flex and BlazeDS demo application

Christophe has posted another really neat demo application that shows BlazeDS collaboration with an application that uses Messaging to allow more than one person to fill out a form at the same time. Not exactly practical in the real world, but a good example if the power of BlazeDS.

Sweet Flex and BlazeDS sample application

Check out this great sample application posted by Christophe Coenraets, complete with instructions and source code, showing a really nice mashup of Yahoo Maps, Flex and BlazeDS.

Monday, April 07, 2008

BlazeDS and LiveCycle Data Services Channels

Back in January, Damon Cooper posted a really great chart of all the channels types and the pros and cons of each written up by Seth Hodgson, a member of the LCDS and BlazeDS teams.

I wanted to post this to my blog for two reasons. 1. In case you missed it and 2. Because I wanted to have a link to it easily available on my blog for future reference.

Boston gets a Flex User Group

The first meeting of the Boston area Flex users group is tomorrow (Tuesday April 8th) at 7:00pm in the Adobe offices in Newton, MA. You can get details at http://www.bostonfug.org/ and to RSVP for the even you should go here.

Pete Farland will be speaking about the open source Flex SDK at the first meeting. I have a dentist appointment that night, so I wont be there, but if you are using Flex and live around Boston, this ought to be worth your time.

ColdFusion 8 Update 1 released

The first update for ColdFusion 8 (aka version 8.0.1) was released this past weekend.

What does is have in it? Quoting from the from FAQ:

ColdFusion 8 Update 1 provides full 64-bit support for several new platforms including versions of Windows, Mac OS X, and Linux, in addition to Solaris (which was added with the initial ColdFusion 8 release). We have also improved functionality in several areas including AJAX functions, CFPDF, and CFIMAGE. We have also updated several software libraries.

Full details on these and other enhancements/fixes are available in the Update Release Notes.

Short answer: Lots of goodness.

To answer one question I have already gotten, there is NO update to the LiveCycle Data Services 2.5.1 bundled in with ColdFusion 8. Nor does the update contain BlazeDS. There are instructions that come with BlazeDS on how to integrate it with ColdFusion (see the resources/ColdFusion directory in the BlazeDS distribution).

Wednesday, April 02, 2008

Facelift for my blog page

Evey once in a while I click through the blogger provided dashboard to see what new things I can do with my blog. Today I managed to improve the fonts and colors, add a picture of myself, and add category information to most of the posts that deserve it. I even added a list of subjects on the left navigation pane of the page so you (and I) can find posts about specific things (like those web service related posts I am always looking for the link to).

If you are reading through an aggregator, come to my page (http://tjordahl.blogspot.com) and check it out.

CFUnited for 2008

I recently got confirmation that I will be attending CFUnited again this year. I believe I will be giving the "Adobe Speaker TBA" talk on ColdFusion 8 and LiveCycle Data Services. I will make sure to include a good discussion about BlazeDS and how that fits in to the picture.

I always enjoy attending CFUnited. It is being held at a new location this year. There probably isn't going to be a Dave & Busters handy to play games at, which is was always a highlight. But the crowd is always good and I really enjoy the sessions. Should be a good time, see you there!