Friday, October 10, 2008    
Home My Books Blog ColdFusion About Me Back    

Calendar
<< Aug 2006 >>
S M T W T F S
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    

Search

Categories
 • Acrobat (2) [RSS]
 • Adobe (68) [RSS]
 • AdobeMAX06 (45) [RSS]
 • AdobeMAX07 (59) [RSS]
 • AdobeMAX08 (25) [RSS]
 • AIR (134) [RSS]
 • Appearances (122) [RSS]
 • Books (69) [RSS]
 • CFEclipse (14) [RSS]
 • ColdFusion (1154) [RSS]
 • Data Services (13) [RSS]
 • Fish Tank (2) [RSS]
 • Flash (106) [RSS]
 • Flex (372) [RSS]
 • Home Automation (3) [RSS]
 • Jobs (96) [RSS]
 • JRun (13) [RSS]
 • Labs (27) [RSS]
 • LiveCycle (22) [RSS]
 • MAX (160) [RSS]
 • Regular Expressions (13) [RSS]
 • RIA (11) [RSS]
 • SQL (38) [RSS]
 • Stuff (505) [RSS]
 • Tips (CF Studio) (80) [RSS]
 • Tips (CF) (795) [RSS]
 • Tips (Dreamweaver) (91) [RSS]
 • Tips (Flex Builder) (2) [RSS]
 • Using CF (137) [RSS]
 • Wireless (99) [RSS]

Other BLOGs
 • Charlie Arehart
 • Lee Brimelow
 • Ray Camden
 • Christophe Coenraets
 • Sean Corfield
 • Mihai Corlan
 • Cornel Creanga
 • John Dowdell
 • Danny Dura
 • Enrique Duvos
 • Steven Erat
 • Kevin Hoyt
 • Serge Jespers
 • Adam Lehman
 • Duane Nickull
 • Miti Pricope
 • Andrew Shorten
 • Ryan Stewart
 • James Ward
 • Greg Wilson
 • Full As A Goog

RSS Feeds
 • Feed
 • Subscribe

Join my mailing list and find out about new books and other topics of interest.

Thoughts, ideas, tips, musings, and pontifications (not necessarily in that order) by Ben Forta ...
NOTE: This is my personal blog, and the opinions and statements voiced here are my own.

Viewing By Month : August 2006 / Main
August 31, 2006

Jeff Peters On The Directory Watcher Event Gateway

I've written and talked about event gateways for a while, and the very first time we ever mentioned gateways publicly was when I demoed the Directory Watcher gateway at CFUNITED 2005. Jeff Peters saw that demonstration, and noting that its significance failed to resonate with many, has written a column providing a very practical use for this gateway.

August 30, 2006

Microsoft XNA Beta Released

Want to write your own games for Xbox 360? Microsoft has announced the availability of the XNA beta, initially to build games for Windows, but eventually to build games for Xbox, too. More details on the FAQ page. Now you have a really compelling reason to learn C#!

August 29, 2006

Class On Using PDF Files And Forms With ColdFusion

Sterling Ledet is offering a class entitled "Using ColdFusion or Java with PDF files and eForms". If you work with PDF files and forms check this out (select "ColdFusion - Using ColdFusion or Java with PDF files and eForms" from the drop down).


Presentation To Thames Valley CFUG

I'll be in the U.K. briefly next month (on my way back from India), and will use the opportunity to present ColdFusion and Flex 2 to the new Thames Valley CFUG. The event is on September 12th and registration is required.


Simple ColdFusion Date Difference Calculations

DateDiff() is usually used to perform date difference calculations. But for simple difference calculations you can subtract dates from each other, like this:

<cfset mydate=CreateDate(2006,8,17)>
<cfoutput>#Now()-mydate# days since #DateFormat(mydate)#</cfoutput>

The returned number will likely not be an integer, and will contain date fractions too, so you may want to use Int() to round the number to just the integer portion:

<cfset mydate=CreateDate(2006,8,17)>
<cfoutput>#Int(Now()-mydate)#</cfoutput>

August 28, 2006

Undocumented Change In <CFFILE> DESTINATION Attribute

I just had to debug some really old code that used to work. I found the issue, and it appears that the code broke in going from CF5 to CFMX (yep, it is code that is not used very often). I was using <cffile action="upload"> to process file uploads, and was using the destination attribute to specify the full path of the file to be saved. As I said, this used to work. But not anymore because destination expects the path to a folder to store the file in, and no longer accepts a path to an actual destination file. The history in LiveDocs says that in CFMX destination was changed so that a trailing slash was no longer needed in paths, but makes no mention of not being able to use actual destination file names anymore. CF5 documentation clearly says that DESTINATION can be a fully qualified file path or directory, so it appears that we broke something in the port from CF5 to CFMX. Bummer.


GetScheduledTasks() Function Returns Scheduled Task List

<cfschedule> is used to define and run ColdFusion scheduled tasks. For some reason the tag does not return a list of defined tasks, and a user on cf-talk asked for this functionality recently. So, until we update <cfschedule>, here is a UDF that returns a query containing the details about all scheduled tasks.

The bulk of this code (the regular expressions, funky nested looping, and so on) are the work of Michael Dinowitz (and I have retained his warnings about not doing what he is about to do).

<!--- Obtain scheduled tasks details --->
<cffunction name="GetScheduledTasks" returntype="query" output="no">

   <!--- Local vars --->
   <cfset var tasks="">
   <cfset var result=QueryNew('path,file,resolveurl,url,publish,password,operation,username,interval,start_date,http_port,task,http_proxy_port,proxy_server,disabled,start_time,request_time_out')>
   <cfset var OuterStart="">
   <cfset var InnerStart="">
   <cfset var qRETest="">
   <cfset var qRETestinner="">
   <cfset var ScheduleItem="">

   <!--- This call is undocumented --->
   <cfsavecontent variable="tasks">
      <cfschedule action="run" task="__list">
   </cfsavecontent>

   <!--- The start for each schedule entry --->
   <cfset OuterStart=1>

   <!--- Be super careful when using an infinite loop in this manner.
      Actually, never use an infinite loop in this manner. --->

   <cfloop condition="OuterStart">
      <!--- Each schedule item is a text string followed by an = followed by a double {.
         The end of the item also has a double }
         Getting only the elements in an item removed the need for a negative lookahead later --->

      <cfset qRETest=REFind('\w+={{(.+?})}}', tasks, OuterStart, 1)>
      <!--- If there is a result, use it.
         Otherwise break out of the loop. VERY IMPORTANT!!! --->

      <cfif qRETest.Pos[1]>
         <!--- This is the string containing all of the
            elements in a schedule item --->

         <cfset ScheduleItem=Mid(tasks, qRETest.Pos[2], qRETest.len[2])>
         <!--- Set the start past the schedule item found --->
         <cfset OuterStart=qRETest.Pos[2]+qRETest.len[2]>

         <!--- The start for each element of a schedule item --->
         <cfset InnerStart=1>
         <!--- Add a row. We don't have so specify as we'll be
            adding 1 per schedule item --->

         <cfset QueryAddRow(result)>
      
         <!--- Be super careful when using an infinite loop in this manner.
            Actually, never use an infinite loop in this manner. --->

         <cfloop condition="InnerStart">
            <!--- A schedule element is text followed by an = followed by
               a value inside of {}. Even though the schedule item string
               can be seen as a list, we don't know if there will be a
               comma inside one of the elements so we're doing it the
               hard but safe way. --->

            <cfset qRETestinner=REFind('(\w+)={([^}]*)}', ScheduleItem, InnerStart, 1)>
   
            <!--- If there is a result, use it.
               Otherwise break out of the loop. VERY IMPORTANT!!! --->

            <cfif qRETestinner.Pos[1]>
               <!--- The QuerySetCell will automatically assign the value to the last row added so no need to specify row. The second element of the RegEx return is the column name and the third is the value--->
               <cfset QuerySetCell(result,
                              Mid(ScheduleItem, qRETestinner.Pos[2], qRETestinner.len[2]),
                              Mid(ScheduleItem, qRETestinner.Pos[3], qRETestinner.len[3]))>

               <!--- Set the start past the schedule element found --->
               <cfset InnerStart=qRETestinner.Pos[1]+qRETestinner.len[1]>
            <cfelse>
               <!--- Break out of our inner infinite loop --->
               <cfbreak>
            </cfif>
         </cfloop>
      <cfelse>
         <!--- Break out of our inner infinite loop --->
         <cfbreak>
      </cfif>
   </cfloop>

   <cfreturn result>

</cffunction>


Mike Nimer's Flex Debug Component

I was working on a Flex component to display ColdFusion debug data in a usable format, but then learned that Mike Nimer had beaten me to it (and created something far more elaborate than I was planning). So, if you are looking for something akin to <cfdump> in Flex (usable with any AS3 objects, including debug information returned from ColdFusion via a <mx:RemoteObject> call), grab a copy of Mike's Flex 2 Debug component.

August 27, 2006

Oliver Merk Explains The ColdFusion Flex Relationship

If you want to learn about the basics of ColdFusion Flex integration, and the relationship between the two, check out Oliver Merk's new CFDJ article entitled An Introduction To Flex 2 For ColdFusion Developers.

August 25, 2006

ColdFusion Positions In MI, KS, MN

It's Friday again, so time for some more ColdFusion employment opportunities:
  • Cleantech Capital Group (Brighton, MI) is looking for an experienced ColdFusion Developer. Requirements include good knowledge of ColdFusion MX 7, HTML, JavaScript, and Dreamweaver This is a full-time onsite position. Contact resume@cleantech.com.
  • City of Overland Park (Overland Park, KS) is looking for a developer to work on existing systems and to develop new applications. Requirements include a Bachelors Degree in Computer Science or a related field, 3+ years of programming experience, and experience with ColdFusion and Oracle. Details (including salary range) are posted online.
  • signsearch (Burnsville, MN) is looking for a junior ColdFusion developer to work on new database and Web application development and the support of existing applications, some support work may be required as well. Requirements include 2+ years working with core technologies HTML, ColdFusion, JavaScript, VBA, and SQL. Details (including salary range) are posted online.


Welcome Another Adobe Blogger, Rahul Narula

Rahul Narula is part of our Bangalore team, some of you met him at CFUNITED a few months ago (where he challenged me to a basketball shootout). Rahul is now blogging. The blog name <cf_rahul> makes it clear that he'll be covering ColdFusion (and he has already posted some goodies), but he also plans on covering Java and Flex as well.

August 24, 2006

Building Flex Tree MXML From ColdFusion Queries

ColdFusion queries are flat and non-hierarchical. The Flex <mx:Tree> control supports data specified in a variety of formats, all of which are hierarchical and not flat. Each time I need to populate a Tree control with dynamic query data I find myself jumping through hoops to convert the data into something Flex can use.

The truth is, what I want is something as simple as ColdFusion's <cftree> tag. That tag lets you pass it a query and a list of columns which it uses to build the nested branches. It does not work for every situation (if you have different levels of nesting, for example) but it does work for most tree driven queries.

So, how do you programmatically build nested XML based on ColdFusion queries?

The simplest solution would be to use <cfoutput group="..." in a <cfxml> block. That way you wouldn't have to figure out the nesting levels, <cfoutput>l does that for you. But that will only work for a fixed number of nested groups, you could use dynamic group names but you'd need to hardcode that number of nested groups because there is no way to programmatically add nesting levels on-the-fly. So much for the simple option.

The next option would be to loop through the data manually, creating a new XML object with XMLNew(), and then adding branches with XMLElemNew(). I spent way too much time on this idea only to discover that it's just not practical. ColdFusion's XML manipulation functions make it really easy to add nodes as long as you know where you want to add them, but there is no easy way to add child elements at arbitrary locations (at unknown depths), and there is no way to search an XML object returning a handle that could be used to create a child at the found location. So, no go.

I tried several other ideas too, until I came up with what follows. Now, before I go further I must state that this is a work in progress. The code is not perfect yet, but it does work, it is flexible, and as I have never seen ColdFusion code that uses query-of-queries recursion like this before I just had to post it in its current state.

So, here goes. The following code block contains two UDFs. BuildTreeXMLProcess() is an internal function is should never be called directly. BuildTreeXML() is the main entry point. You pass it a ColdFusion query, a list of columns specifying your nesting order, and an optional top-level identifier (the default root label is "root"). So, if your query had columns company, department, name, phone, and e-mail, and you wanted the tree to list company at the top level, department beneath it, and name beneath that, you'd specify "company,department,name" as the cols value (it behaves just like <cftree>).

<!--- Internal query recursion function --->
<cffunction name="BuildTreeXMLProcess" returntype="string" access="private" output="false">
   <cfargument name="data" type="query" required="yes">
   <cfargument name="cols" type="string" required="yes">
   <cfargument name="where" type="string" required="no" default="0=0">

   <!--- Local vars --->
   <cfset var result="">
   <cfset var distinctData="">
   <cfset var subData="">
   <cfset var whereClause="">

   <!--- Check if have any more columns in this branch --->
   <cfif ListLen(ARGUMENTS.cols)>

      <!--- Get distinct values for this column --->
      <cfquery dbtype="query" name="distinctData">
      SELECT DISTINCT #ListFirst(ARGUMENTS.cols)# AS col
      FROM ARGUMENTS.data
      WHERE #PreserveSingleQuotes(ARGUMENTS.where)#
      </cfquery>
   
      <!--- Loop through distinct data --->
      <cfloop query="distinctData">
         <cfset whereClause = ARGUMENTS.where & " AND " & ListFirst(ARGUMENTS.cols) & " = '" & col & "'">
         <cfquery dbtype="query" name="subData">
         SELECT *
         FROM ARGUMENTS.data
         WHERE #PreserveSingleQuotes(whereClause)#
         </cfquery>
         <!--- Any more columns in this branch? --->
         <cfif ListLen(ListRest(ARGUMENTS.cols))>
            <!--- Yes, create a node and recurse --->
            <cfset result=result & "<node label=""#col#"">">
            <cfset result=result & BuildTreeXMLProcess(subData, ListRest(ARGUMENTS.cols), whereClause)>
            <cfset result=result & "</node>">
         <cfelse>
            <!--- No, create node and populate with all columns --->
            <cfset result=result & "<node label=""#col#""">
            <cfset result=result & BuildTreeXMLProcess(subData, ListRest(ARGUMENTS.cols), whereClause)>
            <cfset result=result & "/>">
         </cfif>
      </cfloop>

   <cfelse>

      <!--- Bottom of this branch --->
      <cfquery dbtype="query" name="subData">
      SELECT *
      FROM ARGUMENTS.data
      WHERE #PreserveSingleQuotes(ARGUMENTS.where)#
      </cfquery>
      <!--- Write all columns as name="value" pairs --->
      <cfloop list="#subData.ColumnList#" index="column">
         <cfset result = result & " #LCase(column)#=""#subData[column][1]#"" ">
      </cfloop>

   </cfif>

   <!--- And return it --->
   <cfreturn result>
</cffunction>


<!--- Build Flex Tree XML from a query --->
<cffunction name="BuildTreeXML" returntype="xml" access="public" output="no">
   <cfargument name="data" type="query" required="yes">
   <cfargument name="cols" type="string" required="yes">
   <cfargument name="root" type="string" required="no" default="root">

   <!--- Local vars --->
   <cfset var xmlTree="">

   <!--- Populate XML object --->
   <cfxml variable="xmlTree">
   <cfoutput>
   <node label="#ARGUMENTS.root#">
   #BuildTreeXMLProcess(data, cols)#
   </node>
   </cfoutput>
   </cfxml>
   
   <!--- And return it --->
   <cfreturn xmlTree />

</cffunction>

Now, I did say that the code is not quite done yet. The biggest limitation for now is that the columns listed in cols must all be string values (no numbers or dates). I'll fix this at some point. For now, if you need to populate Flex Tree controls with ColdFusion query data, this may help.

If you have comments or suggestions, I'd love to hear them.


Using Dates In Flex Data Filtering

A couple of months ago I posted an example that I wrote to show how to filter a Flex DataGrid on the fly. This afternoon a user asked me how to do filtering using date ranges, allowing a date start and end to be specified so as to filter only data that falls within the specified range.

So, here is an updated example. In the interests of simplicity a local ArrayCollection is used. I have added a Date object member named hired (if you were retrieving data from a back-end like ColdFusion then you would likely return a date type which would be converted to an ActionScript Date class so as to be able to easily perform date calculations).

The UI has two DateField controls, startDate and endDate, and a change to either forces a refresh and the filterFunction is applied. The ArrayCollection filterFunction simply checks that the hired date falls between startDate and endDate (allowing null in case no date is selected).

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
            layout="vertical"
            creationComplete="initApp()">


   <mx:Script>
      <![CDATA[
         // On startup
         public function initApp():void
         {
            // Set filter function
            // Be careful to set filterFunction
            // only after ArrayCollection has been
            // populated.
            myData.filterFunction=processFilter;
         }
      
         // Filter function
         public function processFilter(item:Object):Boolean
         {
            var result:Boolean=false;

            // If start date is null or <= hire date
            // and end date is null or >= hire date
            if ((dateStart.selectedDate == null
                  || dateStart.selectedDate <= item.hired)
               && (dateEnd.selectedDate == null
                  || dateEnd.selectedDate >= item.hired))
            {
               result = true;
            }
            
            return result;
         }
      ]]>
   </mx:Script>

   <!-- Data (use ArrayCollection) -->
   <mx:ArrayCollection id="myData">
      <mx:source>
         <mx:Object name="Ben Forta"
                  location="Oak Park, MI"
                  phone="(248)555-5555"
                  hired="{new Date(2002, 8, 17)}" />

         <mx:Object name="Jane Doe"
                  location="New York, NY"
                  phone="(212)555-1234"
                  hired="{new Date(1999, 12, 16)}" />

         <mx:Object name="Jim Jones"
                  location="Atlanta, GA"
                  phone="(414)555-1212"
                  hired="{new Date(2003, 4, 21)}" />

         <mx:Object name="Roberta Roberts"
                  location="Chicago, IL"
                  phone="(312)555-4321"
                  hired="{new Date(2002, 3, 8)}" />

         <mx:Object name="Steve Stevens"
                  location="Boston, MA"
                  phone="(617)555-5656"
                  hired="{new Date(2006, 6, 14)}" />

      </mx:source>
   </mx:ArrayCollection>

   <!-- UI -->
   <mx:ApplicationControlBar width="100%">
      <mx:Label text="Show from"/>
      <mx:DateField id="dateStart" change="myData.refresh()" />
      <mx:Label text="to"/>
      <mx:DateField id="dateEnd" change="myData.refresh()" />
   </mx:ApplicationControlBar>

   <mx:DataGrid dataProvider="{myData}"
               width="100%" height="100%">

      <mx:columns>
         <mx:DataGridColumn headerText="Name"
                        dataField="name"/>

         <mx:DataGridColumn headerText="Location"
                        dataField="location"/>

         <mx:DataGridColumn headerText="Phone"
                        dataField="phone"/>

         <mx:DataGridColumn headerText="Hired"
                        dataField="hired"/>

      </mx:columns>
   </mx:DataGrid>
   
</mx:Application>

August 22, 2006

Accessing Java Properties

An app I am working on needs to check the Java CLASSPATH (I am trying to return more useful errors if a call fails). The CLASSPATH is a Java property (kind of like environment variables) named java.class.path, and to obtain this value all you need to do is call getProperty() in java.lang.System. To make this simpler I through these two UDFs together, the first returns a structure of all Java properties and the second returns a specific property.

<!--- Return all Java properties --->
<cffunction name="GetJavaProperties" returntype="struct" output="no">
   <cfreturn CreateObject("Java", "java.lang.System").getProperties()>
</cffunction>

<!--- Return a specific Java property --->
<cffunction name="GetJavaProperty" returntype="string" output="no">
   <cfargument name="property" type="string" required="yes">
   <cfreturn CreateObject("Java", "java.lang.System").getProperty(ARGUMENTS.property)>
</cffunction>

It is worth noting that to access a property with a . in its name you'll need to use struct["member"] syntax instead of struct.member. So this will work:

<cfset p=GetJavaProperties()>
<cfoutput>
#p["java.class.path"]#
</cfoutput>

But this won't:

<cfset p=GetJavaProperties()>
<cfoutput>
#p.java.class.path#
</cfoutput>

August 20, 2006

A Couple Of New Adobe Bloggers You Should Know About

Marcel Boucher is the Product Manager in the Technical Marketing Team for the Adobe Enterprise and Developer Business Unit (whatever that means!). In practice, he's a hardcore techie who has been with Adobe for a decade or so, has been involved with LiveCycle for years, is a Java-head ... and he recently saw the light and has become a big fan of both ColdFusion and Flex. And, although I don't want this to go to his head, his first CF apps were rather impressive, some of the best first attempts I have seen! And Marcel has just started blogging, and plans to cover the integration of ColdFusion, Flex, and LiveCycle.

Zhenhua Yang (aka Zee) has also started blogging about Flex and LiveCycle. No CF there yet, we'll have to work on him. ;-)

August 18, 2006

CF Position In Florida

Kforce (Tampa, FL) needs a Cold Fusion Developer for a 3-6 month contract opportunity internally for their Corporate Headquarters. Solid Cold Fusion skills (3-5 years) are required. Mach II is a big plus. Technical experience must also include XML experience. Contact Kevin Melchin.


Introducing Our New ColdFusion Specialist, Adam Lehman

Lots of community folks know Adam Lehman. He's been involved with ColdFusion since the very early days, he founded and managed the Department of State Adobe Developer User Group for over two years, he has created content for the Macromedia DRKs, and he has spoken at many CF events. He is also our new ColdFusion Specialist, based out of our Virginia office, and concentrating on our government and military customers. The greater D.C. area is home to the largest concentration of ColdFusion development and developers, and although Tim Buntel, myself, and other team members spend as much time as I can in the area, we've long needed additional local feet-on-the-street, and now we have them. Welcome aboard, Adam!


ColdFusion Resource Portal

I missed this one while I was out this week, but ... Ray Camden has created ColdFusion Resource Portal, a single page links to all the important ColdFusion links, feeds, resources, and more.

August 11, 2006

Matt Woodward Joining Senate Sergeant At Arms Team

I've known the folks at U.S. Senate Sergeant at Arms for quite a while, they are dedicated CF and Flex shop, and are a great group to spend time with. And now Matt Wooward has announced that he's joining them as Principal Information Technology Specialist. Congrats, Matt!


Special ColdFusion/Flex Promotion For U.S. Federal Customers

For a limited time, Adobe is offering two special RIA bundles exclusively for our federal government customers:
  • RIA Starter Package: Purchase ColdFusion MX 7.0.2 Enterprise and get a free copy of Flex Builder 2 with Charting (a $750 value) plus a free training certificate. Use the training certificate to attend a two hour training session that will show you how to use ColdFusion Extensions for Flex Builder 2 to quickly build data-driven Flex applications that connect to a ColdFusion server.
  • RIA Enterprise Edition: The complete RIA-in-a-Box - one copy ColdFusion MX 7.0.2 Enterprise, one 2CPU license of Flex Data Services 2 Departmental, 2 copies of Flex Builder 2 with Charting plus a free training certificate. Our discounted price for the complete RIA-in-a-Box is over 20% off the list price. Use the training certificate to attend a two hour training session that will provide details on ColdFusion/Flex integration with a focus on integrating ColdFusion with Flex Data Services (FDS).
This promotion is available through select resellers, and expires September 30, 2006. For more information, call us at 1-877-99-Adobe or contact our partner Carahsoft.


GetPDFInfo() UDF Returns PDF Information

A user wrote to ask how the recently released XPPAJ libraries (used in my cf_pdfform tag) could be used to determine basic PDF file information (version, page count, and so on). And yes, it sure can. The following is a quick UDF I threw together that returns PDF version, page count, attachment count, and a flag indicating whether or not the PDF contains a form.

<!--- Uses XPAAJ to return info about a PDF file --->
<cffunction name="GetPDFInfo" returntype="struct" access="public" output="no">
   <cfargument name="PDFFile" type="string" required="yes">

   <cfscript>
   // Init all vars
   var formIS="";
   var PDFfactory="";
   var PDFdoc="";
   var formType="";
   var attachments="";
   var result=StructNew();

   // PDF form input stream
   formIS=CreateObject("java", "java.io.FileInputStream");
   formIS.init(ARGUMENTS.PDFFile);

   // Get PDF document object
   PDFfactory=CreateObject("java", "com.adobe.pdf.PDFFactory");
   PDFdoc=PDFfactory.openDocument(formIS);

   // Get page count and version
   result.pages=PDFdoc.getNumberOfPages();
   result.version=PDFdoc.getVersion();

   // Get formtype object
   formType=PDFdoc.getFormType();   

   // Determine type
   if ((formType EQ FormType.XML_FORM)
      OR (FormType EQ FormType.ACROFORM))
      result.isform=TRUE;
   else
      result.isform=FALSE;

   // Get attachments
   attachments=PDFdoc.getFileAttachmentNames();
   // If have any, get count
   if (IsDefined("attachments") AND IsArray(attachments))
      result.attachments=ArrayLen(attachments);
   else
      result.attachments=0;
   </cfscript>

   <cfreturn result>
</cffunction>

To use the UDF just pass it the fully qualified path to a PDF file, like this:

<cfset PDFFile=ExpandPath("myPDFFile.pdf")>
<cfdump var="#GetPDFInfo(PDFFile)#">

Obviously, XPAAJ must be present to use this UDF.