Friday, July 31, 2009

Installing a Development Mail Server for Testing CFMAIL

Occasionally I need to run some cfmail tests in an environment that does not have access to a mail server, or only a live server I do not want to use for testing. A while back I stumbled across the hMailServer program which is a great tool to have in those situations. Although it can function as a full fledged mail server, it can also be configured to act as a local mail server only. I have not used it in a while, but I found myself needing to install it this week. It is pretty easy to configure. But my memory was a little hazy on a few points. So I thought I would write up some instructions as a reference for next time.

The basic process is very simple. You just install the mail server and create a fake domain with a few email addresses. Then modify the mail server settings to ensure it does not allow messages to be sent to external email addresses (ie the outside world).

The latest stable version (currently 5.2) can be installed in just a few steps. You only need to create an Administrator password when prompted. Then once the installation is complete, log into the Administrator using the password you selected during the installation.



Before you can create email addresses you obviously need a domain. So from the left menu select Domains > Add and enter the desired name on the General tab. I chose the wildly imaginative domain name: psuedo-yahoo.com. Then click Save to actually create the domain.



Now to add an email address, just select your new domain in the left menu and expand it. Then click Accounts > Add. On the General tab enter an address and password and click Save to create the new email address. There are other settings you can configure, but for now address and password are the minimum information needed. (Okay, technically password is not required but it is good sense to assign one anyway). Then repeat the steps to create additional addresses as needed.



Once you have created a few email address, check the Settings > Protocols section and make sure both SMTP and POP3 are enabled. So you can both send and receive emails using the more common protocols.


Since I only wanted to send emails locally, not to the outside world, I selected Advanced > IP Ranges then modified the settings for My Computer so the mail server would only allow deliveries from local to local email addresses. Then I modified the settings for the Internet zone and unchecked all of the delivery options. So nothing was allowed for this zone. (Note, you configure the settings differently but do not check "External-to-External" unless you know what you are doing, as it can create an open relay that might be used for spam).



Now before you start sending emails from ColdFusion, it is a good idea to verify hMailServer is configured properly, using a local email client. I chose Outlook Express because I never use it for email. Under the account settings I entered 127.0.0.1 for both the SMTP and POP servers. So the all emails would only go through the local hMailServer. For user account information, I entered one of the test email addresses I created.



Next, I tried sending a message to an external email address. I used my real email address to be safe. (Just in case I messed up the configuration). As I hoped, my attempt to send a message to an external address was rejected. One test down, one to go.


Finally, I sent a local message to one of the other email addresses for my fake domain. This time the message went through successfully. So I was now ready to start sending mail from ColdFusion. I logged into the ColdFusion Administrator and under mail settings I entered the local IP (127.0.0.1) and one of my fake email addresses:




Once the settings were confirmed, I sent a test message with <cfmail>. Then retrieved it a few seconds later with <cfpop> .. and voila. Instant development mail server.


<!---
Send message
--->
<cfmail to="memyselfandI@psuedo-yahoo.com"
from="cfsearching@psuedo-yahoo.com"
subject="Just Because"
type="html">
<h1>This is a Test</h1>
It is only a test. Had this been a real email,
there would be meaningful content here.
</cfmail>

<!---
Retrieve message
--->
<cfpop action="getAll"
server="127.0.0.1"
username="memyselfandi@psuedo-yahoo.com"
password="#thePassword#"
name="getMail"
/>

<cfdump var="#getMail#"
label="Messages for memyselfandI@psuedo-yahoo.com" />





Obviously there is much more to mail server configuration, but that should be enough to get you started. So if you ever need a development only mail server on windows, consider giving hMailServer a try.

...Read More

Tuesday, July 28, 2009

OT: Food for Thought

Just some food for thought:

The Spirit of Learning

...Read More

Monday, July 27, 2009

ColdFusion: Basic example - Positioning buttons with iText

Recently, a poster named Brook asked about positioning PushButtonFields with iText. Positioning is relatively simple once you understand the default coordinate system. However, if you are like me, that may initially require a slight adjustment in thinking. Unlike top-down systems, the pdf coordinates work from the bottom up, where 0,0 signifies the lower left corner. Also, the default measurement is in points. So you must take both of these things into account in order to position elements.


I threw together a very simple example to demonstrate. The example adds a few buttons to a blank pdf. Each button is placed on a separate page, in the lower right corner. In terms of coordinates, the key part of the code is the section below. It uses a Rectangle object to determine the placement and dimensions of the buttons. The four (4) values passed into the Rectangle represent: the lower left x, lower left y, upper right x and upper right y coordinates.


<cfscript>
box = Rectangle.init( document.right()-72, // llx = lower left x position
document.bottom(), // lly = lower left y position
document.right(), // urx = upper right x position
document.bottom() + 36 // ury = upper right y position
);
// ...
</cfscript>


If you look at the blue angular brackets in the image below, you can easily see how these values combine to determine both size and position of the buttons.



Note, this example uses the JavaLoader.cfc to load a newer version of iText.

<cfscript>
javaLoader = server[MyUniqueKeyForJavaLoader];
fullPathToOutputFile = ExpandPath("./Buttonapalooza.pdf");

PageSize = javaLoader.create("com.lowagie.text.PageSize");
document = javaLoader.create("com.lowagie.text.Document").init(PageSize.LETTER);
outStream = javaLoader.create("java.io.FileOutputStream").init(fullPathToOutputFile);
writer = javaLoader.create("com.lowagie.text.pdf.PdfWriter").getInstance(document, outStream);
Color = javaLoader.create("java.awt.Color");

PushbuttonField = javaloader.create("com.lowagie.text.pdf.PushbuttonField");
Rectangle = javaloader.create("com.lowagie.text.Rectangle");
// the coordinates define both position and size of the field
box = Rectangle.init( document.right()-72, // lower left
document.bottom(), // lower right
document.right(), // upper x
document.bottom() + 36 // upper y
);

document.open();
for (i = 1; i lte 5; i = i+1)
{
button = PushbuttonField.init(writer, box, "buttonField"& i);
button.setText("Button "& i);
button.setBackgroundColor(Color.GRAY);
document.newPage();
writer.addAnnotation(button.getField());
}

document.close();
WriteOutput("Finished!");
</cfscript>


Related articles

...Read More

Saturday, July 25, 2009

ColdFusion 8: Experiment with compiling Java code from ColdFusion - Part 2

In Part 1 I described how you can use JavaCompiler's run() method to compile source code from a java.io.File. However the api also describes how you might extend the SimpleJavaFileObject class so you can compile source code from a string, rather than a physical file.

Since you can now compile code on-the-fly, you can easily use the CF code from part 1 to create their sample class: JavaSourceFromString.




<cfsavecontent variable="javaCode">
import javax.tools.SimpleJavaFileObject;
import java.net.URI;

/**
* A file object used to represent source coming from a string.
*/
public class JavaSourceFromString extends SimpleJavaFileObject {
/**
* The source code of this "file".
*/
final String code;

/**
* Constructs a new JavaSourceFromString.
* @param name the name of the compilation unit represented by this file object
* @param code the source code for the compilation unit represented by this file object
*/
public JavaSourceFromString(String name, String code) {
super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension),
Kind.SOURCE);
this.code = code;
}

@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}
</cfsavecontent>


<cfset pathToInputFile = "C:\myFiles\JavaSourceFromString.java" />
<cfset fileWrite(pathToInputFile, javaCode) />

However, instead of using the JavaLoader this time, we are going to output the class file directly to the WEB-INF\classes\ directory. That way the class will be automatically detected by ColdFusion. Now to redirect the output of the compiler, simply prepend the -d <directory> flag to the list of sources passed into the compiler.


<!--- create a stream to capture errors --->
<cfset errStream = createObject("java", "java.io.ByteArrayOutputStream").init() />

<!--- get a compiler reference --->
<cfset provider = createObject("java", "javax.tools.ToolProvider") />
<cfset compiler = provider.getSystemJavaCompiler() />

<cfset args = [ "-d", "C:\ColdFusion8\wwwroot\WEB-INF\classes\", pathToInputFile ]>
<cfset status = compiler.run( javacast("null", ""),
javacast("null", ""),
errStream,
args
) />

<!--- display the status and any error messages --->
<!--- status: 0 == success --->
<cfset message = toString(errStream.toByteArray()) />
<cfset errStream.close() />
<b>COMPILER RESULTS:</b><hr />
<cfoutput>
STATUS: #status# (0 == success)<br />
ERRORS: #message# <br /><br />
</cfoutput>

Once the code is compiled, ColdFusion should automatically detect the new .class file in WEB-INF\classes\. So if everything went as planned, the new class should be ready to use right away, without rebooting the server. (Note: If you make changes and compile the class a second time, ColdFusion will not automatically reload the class).

Using the new class is very simple. Just create a new instance of JavaSourceFromString, and pass in the name of your class and the source code as strings.

<cfsavecontent variable="javaCode">
import java.util.List;
import java.util.Vector;
public class MyClass
{
public static List<String> test() {
List<String> list = new Vector<String>();
list.add("Congratulations");
list.add("It worked");
return list;
}
}
</cfsavecontent>

<cfset SourceFromString = createObject("java", "JavaSourceFromString") />
<cfset fileObject = SourceFromString.init( "MyClass", javaCode) />
<cfset sourceList = [ fileObject ] />


Now to compile these sources, we are going to try something a little different: using a CompilationTask. This option just provides some additional control over the compile process. We are also going to use a DiagnosticCollector object, which provides an easy way to access error message details.

You can create a CompilationTask using the compiler's getTask() method. Most of the arguments are the same as for the run() method. But there are two additional arguments: options and classNames. The options argument is just an array of settings you want to pass to the compiler. Such as the -d flag used in the previous example. We are going to use that flag again to send the output to the WEB-INF\classes directory. Now to perform the actual compilation, use the call() method.


<cfset provider = createObject("java", "javax.tools.ToolProvider") />
<cfset compiler = provider.getSystemJavaCompiler() />

<cfset collector = createObject("java", "javax.tools.DiagnosticCollector").init() />
<cfset args = ["-d", "C:\ColdFusion8\wwwroot\WEB-INF\classes\" ]>
<cfset task = compiler.getTask( javacast("null", ""),
javacast("null", ""),
collector,
args,
javacast("null", ""),
sourceList
) />
<cfset status = task.call() />


To retrieve any error messages from the DiagnosticCollector, simply call the getDiagnostics() method. It returns an array of Diagnostic objects.


<cfset results = collector.getDiagnostics() />
COMPILER RESULTS:<hr />
<cfoutput>
STATUS: #status# (0 == success) <br />
<cfloop array="#results#" index="diagnostic">
Error on #diagnostic.getLineNumber()#
Starting at #diagnostic.getStartPosition()# <hr />
#diagnostic.getMessage(javacast("null", ""))# <br />
</cfloop>
<br />
</cfoutput>


All that is left is to test the compiled class. If everything went well, your results should look something like this. Ah, cfdump has never looked better ;)


COLDFUSION RESULTS:<hr />
<cfset myObj = createObject("java", "MyClass") />
<cfdump var="#myObj.test()#" label="MyClass.test();" />




As always comments/questions/corrections are welcome.

...Read More

ColdFusion 8: Experiment with compiling Java code from ColdFusion - Part 1

A few months back I was looking through the ColdFusion8\lib directory and noticed the tools.jar file which contained some intriguingly named classes. Needless to say I did not get around to exploring it then, but I finally did get back to it last weekend for a time.

Gold Rush
Now what caught my eye was a class named sun.tools.javac . If you have ever compiled a java class you will undoubtedly recognize the name. For those that have not, javac.exe is a small program used to compile java code from the command line and sun.tools.javac is a java class that allows you to do the same thing, only programatically. So the class definitely has some interesting potential.


Fool's Gold
At first I was very excited about the prospect of being able to compile java code from ColdFusion. But after further research and testing it did not pan out as well as I had hoped. The primary issues were:

  • The sun.tools.javac class is internal and undocumented
  • The sun.tools.javac class was deprecated around version 1.4
  • When I tried using sun.tools.javac from ColdFusion 8, it did compile my source code. But it also bounced the CF Server in the process. (Granted I only ran a few cursory tests after discovering it was undocumented.)

Back to the drawing board I went.

The Real McCoy
Eventually I found the JavaCompiler class, which is an officially supported replacement for sun.tools.javac. The JavaCompiler was introduced in jdk 1.6. (Note the jdk reference. To use the JavaCompiler you must be running a jdk, not just a jre.) As with many java classes, JavaCompiler is designed to be extended so developers can easily add additional functionality. But you can still use the default implementation from ColdFusion, without having to build a custom java class. Fortunately an article on openjdk.com helped fill in some of the gaps in the main api.

Tool Time
Now to use the JavaCompiler I obviously needed some java code. So I created a rather pointless "Clock" class and saved the code to a file on disk. Note, you can use whatever directory you wish, but the file name must match the class name ie use "Clock.java".
<cfsavecontent variable="sourceCode">
import java.util.Date;
public class Clock
{
public Clock()
{
}

public String tick() {
return "tick tock it is: "+ new Date();
}
}
</cfsavecontent>

<cfset pathToSourceFile = "c:\myFiles\Clock.java" />
<cfset fileWrite(pathToSourceFile, sourceCode)>

Next, I grabbed a reference to the system compiler from the ToolProvider class. To compile the code I used the simpler JavaCompiler.run() method, which accepts several arguments. As the openJDK article describes:

The first three arguments to this method can be used to specify alternative streams to System.in, System.out, and System.err. The first two arguments are ignored by the compiler and the last one control where the output from -help and diagnostic messages will go.

So I first created output stream to capture any error messages. Next I created an array of the .java files I wanted to compile. Finally, I called the run() method to actually compile the java code. The result was a brand new .class file located in the same directory as my source file (ie c:\myFiles\ClockClass.class)

<cfset provider = createObject("java", "javax.tools.ToolProvider")>
<cfset compiler = provider.getSystemJavaCompiler()>

<cfset errStream = createObject("java", "java.io.ByteArrayOutputStream").init()>
<cfset args = [ pathToSourceFile ]>
<cfset status = compiler.run( javacast("null", ""),
javacast("null", ""),
errStream,
args
)>

<cfset message = toString(errStream.toByteArray())>
<cfset errStream.close()>
COMPILER RESULTS:<hr>
<cfoutput>
STATUS: #status# (0 == success) <br />
ERRORS: #message# <br />
</cfoutput>

(Drum Roll Please)
All that was left was to test the .class file. For this task I chose to use the JavaLoader.cfc. In its present incarnation, the JavaLoader cannot load individual class files. At least as far as I know. So I used the handy <cfzip> tag to add my new class file to a jar. Finally, I loaded my jar into the JavaLoader and voila, instant results.

Important note: The example below creates a new instance of the JavaLoader on each request for demonstration purposes only. See Using a Java URLClassLoader in CFMX Can Cause a Memory Leak for proper usage tips.

<cfzip action="zip" source="c:\myFiles\" filter="*.class" file="c:\myFiles\clock.jar">
<cfset jarPaths = [ "c:\myFiles\clock.jar" ] >
<cfset javaLoader = createObject("component", "javaLoader.JavaLoader").init(jarPaths)>

<cfset clock = javaLoader.create("Clock").init()>
COLDFUSION RESULTS:<hr>
<cfoutput>
clock.tick() = #clock.tick()# <br/ ><br />
</cfoutput>

<cfdump var="#clock#" label="My Clock class">



The World's Smallest Violin
As JavaCompiler has been around since version 1.6, I had no illusions that I was the first person to discover this tool. Java developers have probably known about it for quite some time. However, I really enjoyed learning about it and how to use it from ColdFusion. For that reason I chose to wait until the end to see if anyone else in the ColdFusion realm had blogged about it. Well, as I went to post this entry last night I was a bit crushed to discover Adrian Walker had just posted something similar two weeks ago. But as our approaches were a bit different, I decided to rise above my disappointment, and post it anyway ;) But seriously, I thought having a bit of extra background might be useful to those interested in using the tool.

So in the vein of "finish what you start" and "but ...{sputter} ... it is already written", more about compiling code with JavaCompiler.CompilationTask in Part 2 ...

...Read More

Thursday, July 23, 2009

SQL Refresher: It is How You Say it That Matters

While growing up, we were all taught that how you say something is just as important as what you say. Imagine if you went into a restaurant and said to the waitress "Fetch me some coffee you lazy slug!". You probably would not receive the coffee for quite some time ( .. and that is a best case scenario). Well, sometimes we forget that databases are no different.

A recent post on cf-talk, by Brad Wood, highlighted exactly this point. He mentioned that using string manipulation functions on a particular column would prevent the proper usage of indexes. Furthermore, it could negatively impact performance as it would likely require a scan of every record in the table. It is a bit like having to read through a two thousand page book looking for a favorite section. You know it is in there somewhere, but without an index you have not a clue where to begin looking for it. So you must resort to reading all of the pages to find it.

Well, to test this theory I created a small table in MS SQL and populated it with about 60,000 records. Then created a non-clustered index on the WidgetName column.


--- Create the sample table
CREATE TABLE Widget
(
WidgetID INT IDENTITY(1,1),
WidgetName VARCHAR(150),
DateCreated DATETIME
)
GO

--- Create the indexes
CREATE INDEX idxWidget_WidgetName ON Widget(WidgetName)
GO
CREATE INDEX idxWidget_DateCreated ON Widget(DateCreated, WidgetName)
GO

--- Populate the table
DECLARE @i INT
SET @i = 0

WHILE @i < 60000
BEGIN
INSERT INTO Widget(WidgetName, DateCreated)
VALUES ( 'Widget #'+ CAST(@i AS VARCHAR), DATEADD(d, @i, getDate()) )

SET @i = @i + 1
END
GO


Finally, I created two versions of a query that searched for records where the WidgetName began with the characters "Widget #111%". Now both queries return the exact same information. They just phrase the request a bit differently.


--- TEST QUERIES
SELECT WidgetName
FROM Widget
WHERE WidgetName LIKE 'Widget #111%'

SELECT WidgetName
FROM Widget
WHERE LEFT(WidgetName, 11) = 'Widget #111'


When I viewed the execution plans, it clearly showed that using the LEFT() function caused the database to perform a less efficient index scan. The index scan essentially touches every record in the index to obtain the desired data. Whereas the index seek locates the data with much greater precision. Notice the overall estimated cost of the second query is very high, relative to the first one (99% versus 1%) as well as its i/o costs. Keep in mind the numbers are relative to the overall batch here. But they do provide a good sense of one query's efficiency over another.



Now this does not just apply to string columns. Say you needed to retrieve a listing of all widgets created in the month of July 2009, sorted by name. In this scenario, you might create a covering index on the DateCreated and WidgetName columns. Again, write two similar queries. Both requesting the same data, just in slightly different ways.


--- Queries on Widget Date
SELECT WidgetName
FROM Widget
WHERE MONTH(DateCreated) = 7
AND YEAR(DateCreated) = 2009
ORDER BY WidgetName

SELECT WidgetName
FROM Widget
WHERE DateCreated >= CONVERT(DATETIME, '20090701', 112)
AND DateCreated < CONVERT(DATETIME, '20090801', 112)
ORDER BY WidgetName


Once again, the execution plans show the query without the functions used the more efficient index seek, with the relative costs being %4 versus %96.



Of course there are always exceptions. For example, if a table contained a small amount of data or a query requests a large percentage of the records from a table, the database engine may decide it is more efficient to perform some sort of scan. So ultimately the database engine makes the final decision. But you can certainly point it in the right direction by writing smarter queries.

Now obviously there is a lot more to analyzing query plans than just reviewing high level numbers. But you can explore the specifics of your database on your own. If you are interested in reading more about one of the more common index types (b-tree), this entry by Matt Fleming is a pretty good read.

...Read More

Small tip: Pre-selecting a series of checkbox options

A question that comes up pretty frequently on forums is how to pre-select a set of form fields on some type of edit page. A typical example being something like this:

"I have an entry form that allows users to select multiple options from a series of checkboxes. When the form is submitted, the user's selections are saved to a database table. Now on my edit page I need to display the same set of checkboxes, but I would like to mark the user's previous selections as checked."





There are several ways to do this, and different reasons you might use each one. However, one option that is often overlooked is to use an outer join to generate the information, rather than two separate queries. Let us say you have two tables: one containing the options and the other the selections of each user.



You could use an outer join and the query would return all records from the Languages table, regardless of whether the user had selected that record or not. With the addition of a small CASE statement, the query could also include a column indicating which options were selected for the current user.


<cfquery name="getLanguages" datasource="#dsn#">
SELECT l.LanguagesID,
l.LanguagesTitle,
CASE WHEN fl.LanguagesID IS NULL THEN 0 ELSE 1 END AS IsSelected
FROM Languages l LEFT OUTER JOIN FavoriteLanguages fl
ON l.LanguagesID = fl.LanguagesID
AND fl.UserID = <cfqueryparam value="#session.userID#" cfsqltype="cf_sql_integer" />
</cfquery>


Then all that is needed is to use the column value to set the initial state of the checkboxes. Voila, the user's previous selections are now checked.


<cfform method="post" action="somePage.cfm" width="400">
<b>Select your favorite languages</b></br>
<cfoutput query="getLanguages">
<cfinput name="LanguageID"
type="checkbox"
value="#LanguagesID#"
label="#LanguagesTitle#"
checked="#IsSelected#" /> #LanguagesTitle# <br/>
</cfoutput>
<cfinput type="submit" name="saveThis" value="Submit" />
</cfform>


I would not say this technique is suitable for every case. But it is something to keep in mind when working with forms.

...Read More

Tuesday, July 21, 2009

SOT: Why JVM's Have No Class (.. or do they?)

So last week I was working with an external jar from ColdFusion and ran into what I thought was your typical ClassNotFoundException. It was perplexing because I knew the jar was accessible to the Classloader. So I could not figure out why the jvm was saying the class could not be found. At the time I was mainly focused on fixing the problem, but something about the exception struck me as odd. Rather than the typical ClassNotFoundException, the error message showed a chained exception that looked something like this:

java.lang.ExceptionInInitializerError
...
...
Caused by: java.lang.NoClassDefFoundError
...
...



With chained exceptions, I have found the first message is often a throw-away error with the true cause being further down the line. So I ignored the initial message and focused on what I thought was a ClassNotFoundException. A great article from the archives of javaworld.com explained why that was a mistake in this case. (Now keep in mind the entry is dated. So not everything in it still applies to later jvm's)

The article explains there are different ways classes can be loaded, each with different implications. When a class is loaded with the Class.forName(..) method, the jvm attempts to initialize the class. That means any static initialization code will be executed as well. If for some reason an error occurs, like during the execution of the static code blocks, the jvm may throw a misleading NoClassDefFoundError. It does necessarily not mean the class cannot be found, rather that it cannot be successfully loaded for some reason.

Perhaps a more elegant and intuitive name for this exception would have been ClassDefinitionLoadError . Oh well. I guess the lesson of the day is, pay attention to those Class exceptions. Sometimes they are tad misleading ;-)

...Read More

Monday, July 20, 2009

Displaying a PDF within CFDIV

I saw a curious question on the adobe forums about displaying a pdf inside a cfdiv tag. When you do this, the pdf is not rendered and instead you just see the bytes of file. I believe this happens because a cfdiv is not a separate window. So you are essentially trying to change the headers mid-stream, which does not work. (I remember there was a good thread on this topic somewhere in the old adobe forums. I am not sure if it is still around.)

Now I do not know if there is a better method, but what does seem to work is embedding the pdf with either <object> or <embed>, depending on your needs. Though not highly tested, it worked for me with Safari, Internet Explorer 8 and FF 3.5. (Naturally this assumes the user's browser is configured to actually display pdf's ;).


<!--- MAIN PAGE --->
<h1>PDF within CFDiv</h1>
<cfdiv bind="url:viewer.cfm" />

<!--- VIEWER PAGE --->
<object type="application/pdf" data="myFile1.pdf" width="400" height="300">

...Read More

Experiment with building your own CFDirectory List in Java - Part 2

Commons Way
When I first explored this idea, I considered using the apache.commons.io package. It has a lot of interesting classes like DirectoryWalker. DirectoryWalker is a nifty class that handles all the tedious work of traversing directories. So you can focus on what you want to do with the results, such as storing the matching file names in a List.

The filefilter package also has a nice set of time-saving classes that handle many common filtering needs. For example AgeFileFilter allows you to search files by date and the aptly named SizeFileFilter allows searching by file size.

You can also chain multiple filters together. The FileFiltersUtils class provides an easy way to combine multiple filters into one. Using the andFileFilter and orFileFilter methods you can create a new filter that will find files that match all of the input filters or any one of the filters. If you need to combine more than two filters, you can also use the AndFileFilter and OrFileFilter classes directly. Both classes have a method that accepts a List of filters.


FileFiltersUtil Example
<cfscript>
Utils = createObject("java", "org.apache.commons.io.filefilter.FileFilterUtils");
// Find files modified on or after July 1st
earliestDate = parseDateTime("2009-07-01");
laterThan = Utils.ageFileFilter( earliestDate.getTime() );
// Find files that are at least 10,000 bytes in size
minimumSize = Utils.sizeFileFilter(10000);

// Create a filter that finds files matching BOTH conditions
findBoth = Utils.andFileFilter(laterThan, minimumSize);
// Create a filter that finds files matching EITHER condition
findEither = Utils.orFileFilter(laterThan, minimumSize);
//...
</cfscript>


My Way
Ultimately, I decided to roll-my-own filter, borrowing a bit of the logic from the DirectoryWalker.walk() method. What I ended up with is pretty simple. The bulk of the class is just your basic getter/setter methods. The only interesting sections are the walk() method, which traverses the directory tree, and the accept() which filters the results to match whatever criteria you supply. You can filter on lastModifiedDate, file size, file type and perform a simple name search or a use a regular expression. The class also allow you to limit the search to a specific number of subdirectories.

protected void walk(File file, int depth, SearchResult results) {
int childDepth = depth + 1;
File[] contents = file.listFiles();
// if this directory level should be processed ..
if (contents != null && (maxDepth < i =" 0;" child =" contents[i];" isallowed =" true;" size =" file.length();" isallowed =" false;" isallowed =" false;" isallowed =" false;"> maxSize) {
isAllowed = false;
}
// File date is EARLIER than the minimum date desired
else if (startDate != null && file.lastModified() < isallowed =" false;"> endBeforeDate.getTime()) {
isAllowed = false;
}
// The name does not match our filter
else if (simpleFilter != null && !simpleFilter.accept(file)) {
isAllowed = false;
}
else if (regexFilter != null && !regexFilter.accept(file) ) {
isAllowed = false;
}

return isAllowed;
}

I wrapped the whole thing in a function and called the class with createObject. Since there is not an easy way to create a CF query object from java, I used the QueryAddColumn hack to convert the results to a query for easy usage.

If you are interested in viewing the code, you can download it from the box.net widget in the right menu. (Since it is loosely based on the DirectoryWalker class, I threw the Apache license in there). The java code is pretty well documented, but questions or suggestions on how to improve it are always welcome.


<!--- USAGE --->
<cfset results = DirectoryList(
directory = "c:\myFiles\",
maxDepth = -1,
type="Dir",
startDate = "01/01/2009",
endBeforeDate = "07/20/2009"
) />

<!--- FUNCTION --->
<cffunction name="DirectoryList" returntype="query" access="public" output="false">
<cfargument name="directory" type="string" required="true" />
<cfargument name="maxDepth" type="numeric" required="false" hint="Maximum directory level to search. Default = 1" />
<cfargument name="type" type="string" required="false" hint="Include only this type of object: File, Dir or All" />
<cfargument name="startDate" type="date" required="false" hint="Include only files modified on or after this date" />
<cfargument name="endBeforeDate" type="date" required="false" hint="Include only files modified on or before this date" />
<cfargument name="minSize" type="numeric" required="false" hint="Minimum file size (in bytes)" />
<cfargument name="maxSize" type="numeric" required="false" hint="Maximum file size (in bytes). Note, directories have a size of zero" />
<cfargument name="regex" type="string" required="false" hint="Regular expression search pattern" />
<cfargument name="filters" type="string" required="false" hint="List of one or more file name filters" />
<cfargument name="delim" type="string" default="," hint="Delimiter for file name filters. Default is a comma ','" />
<cfargument name="makeExclusive" type="boolean" default="false" hint="If true, return files that do NOT match the filters" />
<cfargument name="filterCase" type="string" default="system" hint="Case sensitivity used for file filters" />

<cfset var dir = "" />
<cfset var patterns = "" />
<cfset var results = "" />
<cfset var qry = "" />

<cfif NOT directoryExists( arguments.directory )>
<cfthrow type="InvalidArgument" message="The specified directory cannot be found">
</cfif>

<cfscript>
dir = createObject("java", "org.cfsearching.DirectoryList");

// maximum directory level to search
if (structKeyExists(arguments, "maxDepth")) {
dir.setMaxDepth( arguments.maxDepth );
}

// find only objects of this type
if (structKeyExists(arguments, "type")) {
dir.setFileType( arguments.type );
}

// find only files modified on or after this date
if (structKeyExists(arguments, "startDate")) {
dir.setStartDate( arguments.startDate );
}

// find only files modified on or before this date
if (structKeyExists(arguments, "endBeforeDate")) {
dir.setEndBeforeDate( arguments.endBeforeDate );
}

// find only files at least this size
if (structKeyExists(arguments, "minSize")) {
dir.setMinSize( arguments.minSize );
}

// find only files no larger than this size
if (structKeyExists(arguments, "maxSize")) {
dir.setMaxSize( arguments.maxSize );
}

if (structKeyExists(arguments, "filters")) {
patterns = listToArray(arguments.filters, arguments.delim);
dir.setSimpleNameFilter( patterns, arguments.makeExclusive, arguments.filterCase );
}

if (structKeyExists(arguments, "regex")) {
dir.setRegexNameFilter( arguments.regex );
}

// do the search
result = dir.search( arguments.directory );

// convert results into a query object
qry = queryNew("");
queryAddColumn( qry, "NAME", result.getNameList() );
queryAddColumn( qry, "DIRECTORY", result.getDirectoryList() );
queryAddColumn( qry, "SIZE", result.getSizeList() );
queryAddColumn( qry, "TYPE", result.getTypeList() );
queryAddColumn( qry, "DATELASTMODIFIED", result.getModifiedList() );
queryAddColumn( qry, "FULLPATH", result.getPathList() );
</cfscript>

<cfreturn qry />
</cffunction>


...Read More

Sunday, July 19, 2009

Experiment with building your own CFDirectory List in Java - Part 1

While cfdirectory is a good tag, sometimes you just need just a bit more functionality in the filtering department. A recent question on stackoverflow asked whether it was possible to extend cfdirectory's filtering capabilities to perform an exclusive search (ie find all files that do not end with .cfm). The general consensus was no, and that the simplest work-around is to wrap the cfdirectory call in a function and use a QoQ to get the desired results. However, a poster named Nick piqued my curiosity with his comment about using java file filters. While he agreed it was not the simplest option, it got me to thinking: if you were to try and build your own cfdirectory list function in java, how might you do it?


Aren't there tools that do this already?
Let me say up front, I explored this idea as a learning exercise. I am sure I am not the first person to do this and obviously the simpler solution is to use a few QoQ's to obtain the desired results. But in this case I was more interested in the journey than finding the quickest route to the destination. Along the way I discovered there are several different ways to search a directory with java. But I eventually settled on using file filters, as it seemed the most common method.

Background on file filters
If you have done any work with java from ColdFusion, you have undoubtedly used the java.io.File class. For those that have not, it is a simple class used to represent a path on the file system (and despite the confusing name it can be used for both files and directories). What you may not know is the File class has several methods for retrieving information about the contents of a directory. The simplest is the listFiles() method. It returns an array of other File objects that represent any files or subfolders within a directory. You can easily iterate through that array to display the contents.


<cfscript>
dir = createObject("java", "java.io.File").init("c:/myFiles");
contents = dir.listFiles();
// Display the path of each item within the directory
if (IsDefined("contents"))
{
for (i = 1; i < arrayLen(contents); i++)
{
WriteOutput(contents[i].getPath() &"<br />");
}
}
</cfscript>


Now let us say you wanted to list only the .doc files within a directory. This is where file filters enter the picture. You could create your own custom file filter that determined if a file matched a particular file extension. Then pass that filter into one of the listFiles() methods. The listFiles() method would then use the filter to restrict the results, returning only .doc files in the array.

How do you create a file filter?
There are different types of filters, but in this case you would probably use a FilenameFilter. The FilenameFilter class is actually a very simple interface. To design your own filter, simply create a new class that implements java.io.FilenameFilter. Then implement the mandatory accept(File dir, String name) method. Inside that method is where you would place the logic that checks file extensions. The only requirement of the accept() method is that it return true if a given file matches your criteria. That is about all there is to it. As you can see in the code below, creating your own filter is not that complicated.

Custom Filter

import java.io.File;
import java.io.File;
import java.io.FilenameFilter;

public class FileExtensionFilter implements FilenameFilter{
private String fileExt;
public FileExtensionFilter(String fileExt) {
this.fileExt = fileExt.toLowerCase();
}

public boolean accept(File dir, String name) {
if (name.toLowerCase().endsWith(this.fileExt)) {
return true;
}
return false;
}
}


Using the Filter

Note: The custom class FileExtensionFilter must be added to the CF classpath first. For example, place the compiled class file in C:\ColdFusion8\wwwroot\WEB-INF\classes\

<cfscript>
// Retrieve only .DOC files
dir = createObject("java", "java.io.File").init("c:/myFiles");
// Create the filter
filter = createObject("java", "FileExtensionFilter").init(".doc");
contents = dir.listFiles();
if (IsDefined("contents"))
{
for (i = 1; i < arrayLen(contents); i++)
{
WriteOutput(contents[i].getPath() &"<br/>");
}
}
</cfscript>


Now the only other major piece needed to truly mimic cfdirectory is a bit of recursion. So you can search subdirectories. The basic algorithm in java is the same as it would be in ColdFusion. Just loop through the contents of a directory and check each item. If the item is a directory, repeat the process. Since the algorithm is so similar, I will not go into it here.

Continued in Part 2

...Read More

Friday, July 17, 2009

QoQ: Invalid Column Name Hack (Quick Follow-up)

Just a small note. I noticed the CF9 beta documentation mentions a change in the IsArray function:

Changed behavior: if the value parameter contains a reference to a query result column, this function now returns True. For example: isArray(MyQuery['Column1']) returns True. (In earlier releases, it returns False.)

So the QueryAddColumn hack may be legal in CF9 after all.

...Read More

Thursday, July 16, 2009

CF9 Beta: CFDOCUMENT + OpenOffice Can Convert Any Format to PDF? (Documents,RTF's and Excel Sheets .. oo.Oh!)

Okay, that is definitely an exaggeration. But I have been testing <cfdocument format="pdf" ..> with an OpenOffice 3 installation and was quite pleased to discover it seems like it can convert any format to pdf. Well, any format OpenOffice can convert.

The current version of the CF9 documentation is probably not as clear on this topic as it could be. So I did some poking around. In my initial tests I was able to use CFDocument to convert most Office formats to pdf, just by changing the srcfilevalue. Though I have not tested all of the 2007 formats yet, here are my results so far:

CFDOCUMENT Format="PDF"

Format | Succeeds Yes/No | (Notes)
--------------------------------------
XLS | OpenOffice=Yes | POI=No
XLSX | OpenOffice=Yes | POI=No
DOC | OpenOffice=Yes | POI=No (As documented)
DOCX | OpenOffice=Yes | POI=No (As documented)
PPT | OpenOffice=Yes | POI=Yes
RTF | OpenOffice=Yes | POI=No (This is implied by documentation for .doc)
VSD | OpenOffice=Yes | POI=Semi (No exception, but generated pdf is gibberish)

Overall, the binary formats (.doc, .xls, ...) seemed to be handled a bit better. There were a few formatting issues with some of the more exotic documents I tested, as mentioned here. But since I received the same results using OpenOffice directly, it suggests the issue is with OpenOffice.

My assumption would be that conversion requests from CFDocument are just passed from CF to OpenOffice. So if OpenOffice knows how to do the conversion, the request works. I will probably submit a bug/enhancement request in the public bug database to ask if they can clarify the full range of supported formats in the documentation. There are also a few open and known issues. So if you encounter any issues with the OpenOffice integration, make sure you check the release notes and bug database first.

...Read More

OT: Scope Creep

Scope Creep - 1 result



Scope Creep [skohp] [kreep]
Usage:
You are being a real creep about this. Building a space shuttle is not in the scope of a web application project. End of discussion. No wait... will you be traveling on it?

...Read More

Why Does ArrayAppend Return True and ListAppend Returns the List

Nothing major, but if you are the curious type there was an interesting question today on stackoverflow. It is one of those simple things that you probably use every day, but may not stop to wonder "why is it this way?".

Why does arrayAppend return true and listAppend return the list?

I suspect the mutability issue is probably the biggest reason. But I seem to recall another suggestion (maybe on houseoffusion.com). I believe the poster suggested it might be linked to the fact that arrays are java.util.Vector's. So it is very likely arrays use the Vector.add(Object) method. According to the api the add() method should return a boolean value:

http://java.sun.com/javase/6/docs/api/java/util/Vector.html#add(E)
http://java.sun.com/javase/6/docs/api/java/util/Collection.html#add(E)

But that does raise another question. Are there any circumstances under which ArrayAppend(..) would actually return false?

...Read More

Tuesday, July 14, 2009

Everything is right . Well, except for the fact that it is all wrong (Class Loaders)

Some time back I wrote a CF translation of Paulo Soares' example of How To Sign a PDF with iText. As the java code required some of the newer iText classes, I used the JavaLoader.cfc to access a more recent version of iText. At one stage I ran into a MethodNoFoundException I just could not figure out. It turns out the primary cause was an error in my code and though I eventually figured out the surface cause, the deeper reason for the exception eluded me. But that question always stuck in the back of my mind. While reading about class loaders last week, the real explanation for the error finally hit me.


The Problem
When using the JavaLoader (with the default settings) it is important to be consistent. If you create one object with the JavaLoader, and want it to communicate with other java objects, you must use the JavaLoader to create all of those objects. While that fact seems blatantly obvious to me now, it was not at the time.

At one point in my code I slipped up and used createObject() instead of javaLoader.create() for one of the iText objects. Since CF already has a version of iText in its classpath, the call to createObject() worked and I went blissfully along with my code. The problem did not manifest itself until I tried to get the two objects to talk to each other. Below is a simplified version of the problem. You will notice the code works up until the last line. At that point CF complains that "The add method was not found."

<cfscript>
// get a reference to my javaloader
javaLoader = server[application.MyUniqueKeyForJavaLoader];

// note: the document is created with createObject() and not the javaLoader
document = createObject("java", "com.lowagie.text.Document").init();
document.open();
paragraph = javaLoader.create("com.lowagie.text.Paragraph").init("Hello World");

// This does NOT work
document.add(paragraph);
</cfscript>


The Clue
On the surface it seemed like the code should work. But obviously it did not. I double checked everything from spelling to paths to cfdump'ing the objects. I went so far as to use javap to verify the method signatures. I even tried loading the built-in version of the iText.jar instead. As far as I could tell, everything was correct. Yet still the MethodNotFoundException persisted.

Eventually I figured out the difference was the usage of createObject. It seemed innocuous enough, but it turns out it had a greater effect than I thought. Apparently the children of createObject and javaLoader.create() are like the Hatfields and the McCoys: they do not get along. (At least not with the default settings, which is what I was using). I knew the breakdown in communication had something to do with the fact that the objects were created with two different class loaders: one by the JavaLoader and the other by ColdFusion's class loader. But I was not sure why that made a difference.

The Answer
The light finally dawned while reading a great article in the O'Reilly series. The article provides a detailed description of exactly how class files are loaded. One of the key steps in the process is the findClass() method. Typically, the findClass() method is where the class loader grabs the actual byte codes for a given class. After the bytes are loaded, another method called defineClass() is invoked. That method is responsible for converting the bytes "into an instance of [the given class]". In other words, it creates the new instance for you when you call init() on a java object. Now here is the comment that set off a light bulb in my brain:

... the runtime is very particular about which ClassLoader instance calls [defineClass] ...if two ClassLoader instances define byte codes from the same or different sources, the defined classes are different.
And the sign said "Long-haired freaky people, need not apply"
So my two objects were incompatible because the JavaLoader and the ColdFusion class loader had each generated their own definitions of the class. According to the rules the two definitions are not considered equal. So when I attempted to pass the one object into the other:

<cfscript>
// This does NOT work
document.add(paragraph);
</cfscript>
.. CF spewed out an error message. Not because the document.add(...) method did not exist, but because the existing method would not accept what I was passing into it. I may as well have tried to pass in a brass trombone. From the class loader's perspective, there would be little difference. It would not recognize either one, so both would be rejected equally. (Interestingly, these same rules apply even if you were to use two separate instances of the same ClassLoader class).

So I finally answered that nagging question and temporarily silenced the curious child in me that is always asking "But why?". If you are interested in learning more about class loaders, I would recommend the Internals of Java Class Loading article. Though a bit dated, it provides an excellent overview of the topic.

...Read More

CF9 Beta: Convert .DOC files to PDF (..if you have OpenOffice)

While I knew about the spreadsheet integration in CF9, I was very surprised to discover handling for other Office formats. Specifically the new cfdocument feature that can convert .doc files to .pdf format. Now for the catch: it requires an installation of OpenOffice. But if you can live with that caveat, it is a pretty sweet feature.

I already had some familiarity with OpenOffice, having looked into a few months back. In summary, I was using the JODConverter + OpenOffice + CF8 to convert office documents to pdf. Essentially, OpenOffice is run as a server, and with the help of the JODConverter it recieves incoming requests and returns the converted documents back to CF. Of course CF9's integration with OpenOffice makes the process a whole lot easier.

Configuring OpenOffice is very simple. Just install OpenOffice and then make sure the directory of the OpenOffice program is correctly entered in the ColdFusion Administrator.



That is about all there is to it. Once the setup is complete, you can convert documents to your heart's content using the same old cfdocument syntax:


<cfdocument format="pdf"
srcfile="c:\myFiles\testWord2003.doc"
filename="c:\myFiles\testWord2003_Converted.pdf" />

So far the results for the .doc files I have tested have been very good. Cfdocument did a fairly good job converting images, links, and even creates internal page links when converting a table of contents. Though not surprisingly it did nothing with embedded objects (like an embedded .zip file). The resulting pdf contained only the icon of .zip file, not the binary data itself. Of course there are still a bunch of new attributes to look at, like formfields, formsType and of course the new in memory files feature.

One interesting thing I noticed in the current documentation is that it says:
... the cfdocumenttag lets you read, write, and process Word documents and PowerPoint presentations. All versions of Microsoft office applications from 97 to 2003 are supported.

While it does not mention anything about 2007, OpenOffice is capable of converting .docx files. So I decided to test the theory and feed in a .docx file. Sure enough, it successfully converted the file to .pdf format. Though after testing several files, I began to suspect the omission of 2007 may have been deliberate.

OpenOffice can do a fairly good job of converting .docx files, but it occasionally choked on some of the more elaborate ones. Curiously, one of the files it garbled was one I used when testing the OpenXMLViewer program. I am not sure if it was the vml in the source document, or something else. But with that particular document, OpenXMLViewer and docx4j did a much better job at the conversion than OpenOffice. So perhaps 2007 was not listed as "supported" because there were a larger number of quirks with that format. Still, it would seem to be an option. Though probably an unsupported one ;)

The Man Who Fell to Earth
When I first started testing word converters a few months ago, I think I had some naive idea that there was a magical program out there that could instantaneously transform any file into any format. Obviously there is no such thing. Not in the commercial or open source realm. So if you are envisioning a world of perfect peace, where all file formats exist in complete harmony, one hundred percent of the time .. go back to bed. You are still dreaming. But if you take a more level-headed approach, and keep in mind some of the practical limitations, you may find that the available tools can get you pretty darn close.

Note, my comments above are not intended to disparage the new cfdocument features, or any of the tools I have tested. But the fact of the matter is the Word format is incredibly complex and more importantly, fluid. In some ways, even more so than Excel spreadsheets. Add in the conversion to a completely different file format (pdf) and there is definitely room for some "creative interpretations" . So my recommendation would be do not make yourself crazy over every minor formatting difference or you will end up in a padded cell in no time.

Now, back to our regularly scheduled program.

Related Entries:
CF9 Beta: CFDOCUMENT + OpenOffice Can Convert Any Format to PDF? (Documents,RTF's and Excel Sheets .. oo.Oh!)


...Read More

Monday, July 13, 2009

CF9 Beta: Small POI and OOXML Tip

I noticed the CF9 beta uses POI version 3.5-beta5-20090506. I am assuming it is what powers the cool new <cfspreadsheet> tag.

Now I do not know if POI is used for anything else, but the nice thing about version 3.5 is that it includes ooxml handling. That means you can use neat tricks like these, right out out of the box. No extra jars or javaLoader needed.

...Read More

CF9 Beta: What version of iText does it use?

Since I love everything iText, one of the first things I did after installing the beta was check the iText version. (For the lazy, it is iText 2.1.0).

How to determine the version of iText

...Read More

CF9 Beta: Finally, CFFinally

Though not a major feature, I am very pleased to see the cffinally tag in the ColdFusion 9 Beta. I have always missed the full try/catch/finally construct found in other languages.

While you could manually structure your code to simulate it, the absence of a finally clause makes it easier to overlook important cleanup tasks. For example, when working with lower level java objects it is easy to forget to close file objects or database connections. That can lead to problems with locked files or too many open connections. So I think the additional of finally will help promote better coding habits.


CFScript Example:


<h2>IText - HelloWorld Example with CFScript / Finally</h2>
<cfscript>
outputFile = ExpandPath("./HelloWorld.pdf");
document = createObject("java", "com.lowagie.text.Document").init();
outStream = createObject("java", "java.io.FileOutputStream").init(outputFile);

try {
writer = createObject("java", "com.lowagie.text.pdf.PdfWriter").getInstance(document, outStream);
document.open();
paragraph = createObject("java", "com.lowagie.text.Paragraph").init("Hello World");
document.add(paragraph);
// Deliberately cause an error
z = 10 / 0;
}
catch (java.lang.Exception e) {
WriteOutput("Entering Catch Clause! Message:"& e.message &"<br>");
}
finally {
WriteOutput("Entering Finally Clause!" &"<br>");
if (structKeyExists(VARIABLES, "document") && document.isOpen()) {
document.close();
}
if (structKeyExists(VARIABLES, "outStream")) {
outStream.close();
}
}
</cfscript>

CFML Example:

<h2>IText - HelloWorld Example with CFFINALLY</h2>
<cftry>

<cfset outputFile = ExpandPath("./HelloWorld.pdf") />
<cfset document = createObject("java", "com.lowagie.text.Document").init() />
<cfset outStream = createObject("java", "java.io.FileOutputStream").init(outputFile) />
<cfset writer = createObject("java", "com.lowagie.text.pdf.PdfWriter").getInstance(document, outStream) />
<cfset document.open() />
<cfset paragraph = createObject("java", "com.lowagie.text.Paragraph").init("Hello World") />
<cfset document.add(paragraph) />
<!--- Deliberately cause an error here --->
<cfset z = 10 / 0 />

<cfcatch type="any">
Entering Catch Clause! Message: <cfoutput>#cfcatch.message#</cfoutput><br/>
</cfcatch>

<cffinally>
Entering Finally Clause!<br/>
<cfif structKeyExists(VARIABLES, "document") AND document.isOpen()>
<cfset document.close() />
</cfif>
<cfif structKeyExists(VARIABLES, "outStream")>
<cfset outStream.close() />
</cfif>
</cffinally>
</cftry>

...Read More

Sunday, July 12, 2009

Good Things Come To Those Who .... Stay Up Late (ColdFusion 9 Anyone?)

I was pleasantly surprised to see these announcements a short while ago:

ColdFusion 9 and ColdFusion Builder in the wild

Adobe releases public beta of ColdFusion 9 and ColdFusion Builder

...Read More

Friday, July 10, 2009

Java: Eclipse Tip / Change a "General" to a "Java" project

A few weeks ago I had problems importing a java project into Eclipse. I ended up with a "General" project, instead of a "Java" project, which meant the build path option was disabled. With the help of this useful tip it was fixed in seconds.

Eclipse: Changing a General project to a Java project

...Read More

Wednesday, July 8, 2009

SOT: Why do people forget about INSERT/SELECT statements (I-Scream)?

A recent response by Peter Boughton on stackoverflow.com pointed out a commonly overlooked technique for handling multiple selections from a set of database values. Well it reminded me that I wanted to write a small entry on the bizarrely underused INSERT/SELECT method. (Now chances are if you are already familiar with it, you are probably not reading this entry ;) So in summary, a SELECT statement can be used to INSERT multiple records into another table. Frankly, it is so simple I do not know why it is not used more often.

Take this frivolous form as an example. It uses your typical query to generate a series of checkboxes. The checkboxes are given the same name, so the selected id's will be submitted as a comma delimited list. Nothing earth shattering here.


Code

<cfquery name="getFlavours" datasource="#MyDSN#">
SELECT FlavourID, FlavourName
FROM IceCreamFlavour
ORDER BY FlavourName
</cfquery>

<form method="post" action="saveFavorites.cfm">
<b>Select your favorite ice cream flavours:</b><br/><br/>
<cfoutput query="getFlavours">
<input type="checkbox" name="flavourID" value="#FlavourID#">#FlavourName#<br/>
</cfoutput>
<br/>
<input type="submit" name="saveMe" value="Save My Flavours!" />
</form>



Form



Now when it comes time to inserting the selected values into a database, you will often see code using a <cfloop> to INSERT the values one-by-one. While that does work, it is not the most efficient technique because it requires multiple queries. That creates another disadvantage. With multiple queries, there is always the possibility of leaving the database in an inconsistent state if an error occurs. Of course that can be solved with <cftransaction>, but there is a simpler option.

Since the source of the values is a database table, you can use a SELECT statement combined with an IN (...) clause to retrieve the id's selected and INSERT them into another table. All within the same query.

Validation omitted for brevity

<cfquery name="saveData" datasource="#MyDSN#" result="results">
INSERT INTO FavoriteFlavour ( UserID, FlavourID )
SELECT <cfqueryparam value="#session.userID#" cfsqltype="cf_sql_integer">,
FlavourID
FROM IceCreamFlavour
WHERE FlavourID IN
(
<cfqueryparam value="#form.flavourID#" list cfsqltype="cf_sql_integer">
)
</cfquery>

As a side benefit, you also get some extra validation. Since a SELECT is used to retrieve the values, only valid record id's will be inserted into the second table. While not the solution for every INSERT, you might be surprised at how flexible and useful this type of INSERT can be.

...Read More

Monday, July 6, 2009

Four and Twenty Blackbirds Baked in a Pie Chart

I read a question last week about creating a flash cfchart pie chart with a transparent background. For whatever reason I was convinced this was a simple task, and could have sworn I had done it before. Well it seems I was wrong and must now eat crow.

After checking the trusty webchart3d utility I did find a transparent setting for background color. But it appears to do nothing for png charts. They are always saved as opaque. Eventually, I did find a hack for flash charts (only). But it does require tweaking the output a bit.

First create a custom style file, adding the tranparent background property. The simplest way is to make a copy of C:\ColdFusion8\charting\styles\default_pie.xml. Save it to the same directory as your cfm script as transparent_pie.xml. Then add the transparent background property towards the end, just before the closing </pieChart> tag:

<?xml version="1.0" encoding="UTF-8"?>
<pieChart depth="Double" style="Solid" angle="340" is3D="false">
...
<insets left="5" top="5" right="5" bottom="5"/>
<background type="Transparent"/>
</pieChart>

Next, use the custom style to generate the cfchart. But capture the output with cfsavecontent so you can manipulate it.

<cfsavecontent variable="chartOutput" >
<cfchart format="flash" style="transparent_Pie.xml">
<cfchartseries type="pie">
<cfchartdata item="Yes" value="750">
<cfchartdata item="No" value="550">
</cfchartseries>
</cfchart>
</cfsavecontent>

Finally, use a few string functions to insert the "wmode" setting to make the background of the swf transparent.

<!--- add "wmode" parameter to OBJECT tag --->
<cfset newParam = '<param name="wmode" value="transparent">'>
<cfset newOutput = reReplaceNoCase(chartOutput, "(<OBJECT [^>]+>)", "\1"& newParam, "all")>
<!--- add "wmode" attribute to EMBED tag --->
<cfset newParam = 'wmode="transparent"'>
<cfset newOutput = reReplaceNoCase(newOutput, "(<EMBED [^>]+)", "\1 "& newParam &" ", "all")>

Now the chart's background will be transparent. Definitely a hack. But if anyone knows of a cleaner work-around, I would love to hear it.

<!--- display the chart --->
<div style="background-color: #ff0000;">
<cfoutput>
#newOutput#
</cfoutput>
</div>

...Read More

Saturday, July 4, 2009

QoQ: Invalid Column Name Hack

I saw a question the other day about how to rename query columns with invalid names. Now in the poster's case, renaming really was not needed. Like most query ills it was solved with a simple bit of array notation: #queryName["column-name"][rowNumber]#

But in the process I did stumble upon another way to rename a column. In a pinch, you can use QueryAddColumn() to make a copy of it under a new name. Just pass in a reference to the query column in place of the array of values. Though this verges on the undocumented, it does work with both MX7 and CF8:


<cfset queryAddColumn(myQuery, "BetterColumnName", myQuery["Awful-Column-Name"]) />


Why does it work?
When you add a column with the QueryAddColumn() function, the values are passed in as an array. If you take a look at that function internally, you will see that the argument type is actually a java.util.List

<cfset funcObject = createObject("java", "coldfusion.runtime.QueryFunction")>
<cfdump var="#funcObject#" label="Query Functions">



Since ColdFusion arrays are java.util.List objects internally, that makes sense. But that also means that you can pass in any java.util.List object.

Now if you create a query, and examine one of its columns, you will discover the query column is also a java.util.List object. That means the QueryAddColumn() function will accept it the same way it would a ColdFusion array object.


<cfset myQuery = QueryNew("SomeColumn")/>

<cfoutput>
<b>Query Column Class:</b> #myQuery.SomeColumn.getClass().name#<br/>
<cfset interfaces = myQuery.SomeColumn.getClass().getInterfaces() />
<!--- Show the interfaces this class impements --->
<cfloop array="#interfaces#" index="inter">
Implements: <cfdump var="#inter.getName()#" label="Interface"/><br/>
</cfloop>
</cfoutput>

So there you have it. Again, this is not documented behavior. But I thought it was an interesting find nonetheless.

Related Entry: QoQ: Invalid column Name Hack (Follow-up)

...Read More

Friday, July 3, 2009

OT: Null Characters Bring out the Beast

Once every blue moon, I am reminded of the fact that you cannot create a null character in ColdFusion. At least not in any of the ways you would expect.

When the moon rises, I quickly run through the logical possibilities chr(0), javacast("char", chr(0)), etcetera. Only to find they do not work. Each new attempt producing everything from a space character to an empty string. But no null character in sight.

As my frustration grows, my nails begin to lengthen. My flesh erupts in fur and my teeth transform into fangs. My nails gouge the keyboard as I begin to howl. Then I remember. There is a way. Somewhere in the tombs of google there is an ancient article on Null Characters in ColdFusion.

Searching google as quickly as my claws will allow, I find it. My fangs begin to recede. I try it. My fur transforms back into flesh. It works. I am safe for another few years.

Good thing too, because trying type with claws is pretty rough on the average keyboard. Well, I am off to the computer store to buy a new one. Maybe I will pick up two .. just in case.

(Yes, it has been that kind of a day. Grrrr)

...Read More

Wednesday, July 1, 2009

ColdFusion: Experiment Converting MS Word to HTML/PDF - Part 2 Example

A brief addendum to Part 2. Here is an example of using a OpenXMLViewer.exe from a .bat file. Just update the directory to match the location of the OpenXMLViewer folder on your system.

Note, the cd (change directory) statement in the first line is important. If you have not added the OpenXMLViewer folder to your PATH environment variable, you must call the program from within the parent directory so it can locate the needed dependency files.

BAT File


@ECHO OFF
REM **********************************
REM %1 Full path to the source file *.docx
REM %2 Full path to output folder (ie directory not file name)
REM %3 Browser type (IE, FIREFOX or OPERA)
REM **********************************

cd % C:\tools\OpenXMLViewer\
OpenXMLViewer.exe %1 %2 %3


ColdFusion Code
<!---
Initialize file paths
--->
<cfset inputFilePath = ExpandPath("Introduction to Microsoft .NET Services.docx")>
<cfset outputFolder = ExpandPath("test")>
<cfset pathToProgram = ExpandPath("openXMLViewerConvert.bat")>
<cfset browserType = "FIREFOX">

<!---
OpenXMLViewer does not seem to create the output
folder if it does not exist. So ensure it exists
before doing the conversion.
--->
<cfif NOT DirectoryExists(outputFolder)>
<cfdirectory action="create" directory="#outputFolder#">
</cfif>

<!---
Do the conversion
arguments='/c "#pathToProgram#" "#inputFilePath#" "#outputFolder#" #browserType# 2>&1'
--->
<cfexecute name="c:\windows\system32\cmd.exe"
arguments='/c #pathToProgram# "#inputFilePath#" "#outputFolder#" #browserType# 2>&1'
timeout="120"
variable="result" />

<!---
Display the generated html file
--->
<cfdirectory action="list" directory="#outputFolder#" name="getHTMLFiles" filter="*html*">

<h3>Generated HTML</h3>
<cfoutput query="getHTMLFiles">
<a href="#ListLast(directory, '\/')#/#Name#">Display HTML (#Name#)</a>
</cfoutput>

<cfdump var="#result#" label="Results from Cfexecute">

...Read More

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep