Thursday, December 24, 2009

CF9: One Difference Between the New Ternary Operator and IIF()

When I first saw the ternary operator in java, I confess I did not like it. To my way of thinking, it was odd and cryptic, and I did not see the advantage over a standard if/else statement. Well, I have come a long way. For simple comparisons I now prefer its sleeker syntax over the more bulky if/else construct.

   <cfset someCondition = true />
<cfif someCondition>
<cfset foo = 123>
<cfelse>
<cfset foo = 321>
<cfset bar = 88>
</cfif>


If you are using the new ternary operator, a statement like the one below would succeed. It does not matter that the variable bar is not defined. Because the second variable is only evaluated when someCondition is false.
 
<cfset result = someCondition ? foo : bar />

However, using IIF(), both variables are evaluated immediately, whether they are used or not. So this code would result in an error: "variable BAR is undefined"

 
<cfset result = IIF(someCondition, foo, bar) />
To avoid the error you would need to use the DE() function, which just feels awkward to me.

<cfset result = IIF(someCondition, foo, DE("bar")) />

So in this sense at least, the ternary operator is a bit smarter than the old IIF() function.

...Read More

Wednesday, December 23, 2009

Merry Christmas To All

I just wanted wish everyone in the CF community a Merry Christmas. I hope you all have a wonderful holiday. See you next year!

(My favorite holiday tune)


Why NORAD Tracks Santa

Tuesday, December 22, 2009

Wrap and Scale Text (The Ghost of Code Past)

A long time ago, I wrote a cfc for wrapping and scaling image text to fit within a given set of dimensions. I always thought it was something better done in java. Well, I finally got around to creating a java version, and it is a lot simpler to use. I also improved the algorithm used to auto-scale the text, and may write up an entry on that later. Plus I finally added support for line breaks (left-to-right text only).






Update 2009-12-22: Fixed wrong file version in source

...Read More

Wednesday, December 16, 2009

ColdFusion + Tomcat + Apache + Windows (Take the plunge)

For a while now I have wanted to experiment with other CF engines. But kept putting it off for various reasons. Well, this weekend I decided to take the plunge. My first attempt was using CF9's multi-server install with JRUN which met with only partial success. For whatever reason I was never able to get the Open BlueDragon WAR file to deploy correctly under JRUN.

Thanks to a suggestion from Peter J. Farrell, I decided to try using Tomcat instead. Surprisingly, it was very simple to setup. I followed some excellent posts by Todd Rafferty and Matt Woodward and now have all three engines up and running with Tomcat + Apache on windows (.. and yes Todd, it is very fast!). Now be warned, Matt's entry is long. But it is very informative, so read it all. (Besides, if you read my blog I know you are used to long entries.)

Now I am busily reading up on Tomcat and Apache, wondering why I did not do this sooner...


Update: When I installed Tomcat I selected a different JVM than the one that ships with CF9. So the first time I tried to invoke a web service CF threw an error saying the "...class file has wrong version 49.0, should be 48.0". In case you run into this issue, it was easily solved after reading a handy post by Rupesh Kumar: ColdFusion and WebService : "class file has wrong version. Note, after updating the tools.jar, I did have to stop and restart Tomcat for the change to fully take effect.

Monday, December 7, 2009

OT: Easter Eggs

In a recent post on stackoverflow.com, member Sergii mentioned a ColdFusion easter egg. (Thanks Sergii!) Go ahead and feel smug if you already knew about it. If not, check it out. It is ... interesting.

While searching for others, I came across a few other fun ones. Not all CF-related of course. But there are definitely some strange and amusing ones out there.

Various Programs (source: www.stackoverflow.com)
http://stackoverflow.com/questions/397576/whats-your-favorite-easter-egg

JQuery *This one rocks!* (source: www.aliaspooryorik.com)
http://www.aliaspooryorik.com/blog/index.cfm/e/posts.details/post/136

Google (source: www.danvega.org)
http://www.danvega.org/blog/index.cfm?mode=cat&catid=CCF3C429-AE69-270C-3D2E62AA9CC59D70

RIA Unleashed : Boston Site (source: www.remotesynthesis.com)
http://www.remotesynthesis.com/post.cfm/announcing-ria-unleashed-boston
* See the P.S. at the bottom

Friday, December 4, 2009

Fedex Web Services + createObject("webservice") == Migraine

A very long time ago, I wrote an entry on my novice struggles using the Fedex web services with createObject(). Though the entry shows I still had a lot to learn about web services, I still get occasional questions about it. In particular, about working with the Fedex web services and createObject(). So I will offer this advice to anyone looking to do the same - don't.

While ColdFusion definitely simplifies the process of working with web services, it is also not the best tool for every web service. Especially the more complex ones. If you have ever tried using the FedEx web services with createObject("webservice"), you know how painful it can be. They are extremely complex and deeply nested. So unless you miraculously hit on the right code the first time around, you are likely to waste a LOT of time trying to troubleshoot cryptic Axis error messages .. and the time you spend will not be fun.

So my recommendation is to try using SOAP + CFHTTP instead. The Fedex WSDL developer files include working SOAP samples. They will get you up and running much faster than trying to translate the java examples into CF code. An old entry on Russ Michaels blog has some good tips on working with SOAP. There is also a current project using the SOAP approach with the Fedex Rates Web Service, under the project name CFFedexRates. You could easily use it as a base for some of the other web services.

Take my word for it, you will be glad you did.

Update December 9, 2009:
For a version of CFFedexRates that is compatible with the Rate Service v7, see issue #2 http://code.google.com/p/cffedexrates/issues/detail?id=2


Update January 21, 2010:
The featured downloads section was updated to include a version compatible with Rate Service v7.

Update May 4, 2010:
For a version of CFFedexRates that is compatible with the Rate Service v8, see issue #4 http://code.google.com/p/cffedexrates/issues/detail?id=4

Thursday, December 3, 2009

ColdFusion: Encryption Interoperability Issues (Beginner)

Recently, I have seen a few questions about encryption interoperability. The libraries used by ColdFusion (Sun JCE) are pretty standard. So for the most part, compatibility should not be issue. But it got me to thinking about some of the common pitfalls when trading encrypted data with external tools. While they may seem obvious to some of you, many of them were not at all obvious to me.

                    ColdFusion            C# (RijndaelManaged)
Mode: ECB CBC
Padding: PKCS5Padding PKCS7
Key Size: 128 bit 256 bit
Block Size: 128 bit 128 bit
Allowed Key Lengths 128, *192, *256 bit 128, 192, 256 bit

* Note: Unlimited encryption is required in CF for keys larger than 128 bit

Once you have aligned the cipher settings, double check the encoding used on the various values. In ColdFusion the string to be encrypted/decrypted "is always interpreted as a UTF-8 string". From what I observed, the key value seems to be interpreted as base64. So using this collective information you can now easily adjust the settings of ColdFusion and C# to produce the same results.
<!---
Encrypt/Decrypt
---->
<h3>ColdFusion (AES/CBC/PKCS5Padding) + iv</h3>

<cfset thePlainData = "Nothing to see here folks" />

<cfset theKey = generateSecretKey("AES", 128) />
<cfset theAlgorithm = "AES/CBC/PKCS5Padding" />
<cfset theEncoding = "base64" />
<cfset theIV = BinaryDecode("7fe8585328e9ac7b7fe8585328e9ac7b", "hex") />

<cfset encryptedString = encrypt(thePlainData, theKey, theAlgorithm, theEncoding, theIV) />
<cfset decryptedString = decrypt(encryptedString, theKey, theAlgorithm, theEncoding, theIV) />


<!---
Display results
--->
<cfset keyLengthInBits = arrayLen(BinaryDecode(theKey, "base64")) * 8 />
<cfset ivLengthInBits = arrayLen(theIV) * 8 />
<cfdump var="#variables#" label="AES/CBC/PKCS5Padding Results" />



using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;

namespace AESTest
{
public class AESCBC
{
public static void Main()
{
try
{
// Just hard coded values for testing ...
// MUST change them to match the values used in the CF code
String thePlainData = "Nothing to see here folks";
String theKey = "oRJUjgbx9SGGR6v3T8JGJg==";
String theIV = "f+hYUyjprHt/6FhTKOmsew==";
String encryptedText = EncryptText(thePlainData, theKey, theIV);
String decryptedText = DecryptText(encryptedText, theKey, theIV);

Console.WriteLine("Encrypted String: {0}", encryptedText);
Console.WriteLine("Decrypted String: {0}", decryptedText);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}

Console.ReadLine();
}


public static String EncryptText(String Data, String Key, String IV)
{
// Extract the bytes of each of the values
byte[] input = Encoding.UTF8.GetBytes(Data);
byte[] key = Convert.FromBase64String(Key);
byte[] iv = Convert.FromBase64String(IV);


// Create a new instance of the algorithm with the desired settings
RijndaelManaged algorithm = new RijndaelManaged();
algorithm.Mode = CipherMode.CBC;
algorithm.Padding = PaddingMode.PKCS7;
algorithm.BlockSize = 128;
algorithm.KeySize = 128;
algorithm.Key = key;
algorithm.IV = iv;

// Create a new encryptor and encrypt the given value
ICryptoTransform cipher = algorithm.CreateEncryptor();
byte[] output = cipher.TransformFinalBlock(input, 0, input.Length);

// Finally, return the encrypted value in base64 format
String encrypted = Convert.ToBase64String(output);

return encrypted;
}

public static String DecryptText(String Data, String Key, String IV)
{
// Extract the bytes of each of the values
byte[] input = Convert.FromBase64String(Data);
byte[] key = Convert.FromBase64String(Key);
byte[] iv = Convert.FromBase64String(IV);


// Create a new instance of the algorithm with the desired settings
RijndaelManaged algorithm = new RijndaelManaged();
algorithm.Mode = CipherMode.CBC;
algorithm.Padding = PaddingMode.PKCS7;
algorithm.BlockSize = 128;
algorithm.KeySize = 128;
algorithm.Key = key;
algorithm.IV = iv;

//FromBase64String
// Create a new encryptor and encrypt the given value
ICryptoTransform cipher = algorithm.CreateDecryptor();
byte[] output = cipher.TransformFinalBlock(input, 0, input.Length);

// Finally, convert the decrypted value to UTF8 format
String decrypted = Encoding.UTF8.GetString(output);

return decrypted;
}
}
}

Hopefully these small tips will help someone else or at least keep them from pulling out their hair trying to coerce ColdFusion and some other tool into producing the same results.

...Read More

Wednesday, December 2, 2009

How to Move A Directory

The round-a-bout way to move a directory always slips through my brain like it is a sieve. So this one is a reminder to myself. Use <cfdirectory action="rename"..>, or in ColdFusion 9 use the sleeker DirectoryRename() function. Note, an error will occur if the destination directory already exists.

<cfset sourceDirec ="C:\docs\images\20091202">
<cfset destDirec ="C:\20091202">

<cfdirectory action="rename"
directory="#sourceDirec#"
newDirectory="#destDirec#" />

Saturday, November 28, 2009

OT: This is Unicode ?

While visiting the unicode.org site, I came across this unexpected title from 1992. (Yes, it really is about unicode. Who says unicode is a dry topic ;).


Kiss Your ASCII Goodbye (John Dvorak, PC Magazine/1992)

Friday, November 27, 2009

SOT: Heads or Tails? (Windows Resource Kit - Tail.exe Gotcha)

So I was comparing two windows implementations of tail, and came across something unexpected. While I was checking one of my tests, I noticed the results contained less lines than I requested. The base files definitely contained more than enough lines. So I knew that was not problem. After digging deeper, I discovered the lines returned were not even from the end of the file! They were from the middle.

<cfset specialChar  = chr(26)  />
<cfset newLine = chr(10) />
<cfsavecontent variable="data">
<cfoutput>
<cfloop from="1" to="50" index="x">Log file line #x# <cfif not x mod 10>#specialChar#</cfif>#newLine#</cfloop>
</cfoutput>
</cfsavecontent>

<cfset filePath = ExpandPath("./testLogFile.log") />
<cfset FileWrite( filePath, trim(data) ) />


Now use tail.exe to grab the last twenty (20) lines of the file. Due to the placement of the special characters, what you will end up with is the first 10 lines of the file, not the last twenty.




<cfset grabLines = 20 />
<cfexecute name='"c:\program files\Windows Resource Kits\tools\tail.exe"'
arguments='-#grabLines# #filePath#'
variable='output'
timeout='20' />

<!--- convert lines to array --->
<cfset linesFound = listToArray(output, chr(10), true) />
<cfset actualLines = listToArray( FileRead( filePath ), chr(10), true) />

<cfoutput>
<p><strong>Log File:</strong> #filePath# </p>
<p><strong>Requested Lines:</strong> #grabLines# </p>
<p><strong>Lines Found:</strong> #arrayLen(linesFound)#</p>
<p><strong>Actual Lines:</strong> #arrayLen(actualLines)#</p>
</cfoutput>


Fortunately, the GNUWin32 version does not suffer from this "problem" and returns the expected results. (Thank goodness someone knows basic subtraction)



As for the Windows Resource Kit version, well.. if we were flipping a coin, this time it was definitely "heads" - as in "head on over to sourceforge.net and get a yourself a working copy of tail.exe".

...Read More

Wednesday, November 25, 2009

OT: JAI Resources?

Does anyone know of any good resources (online, books, etcetera) on JAI? Frankly the sun docs leave a lot to be desired. If anyone can recommend a good book on the topic it would be greatly appreciated.

Tuesday, November 24, 2009

ColdFusion 8: Blowfish Encryption (Something smells fishy .. and it is not the algorithm)

A while back I saw a thread on the adobe forums about an issue with Blowfish encryption. The issue being the results from ColdFusion's encrypt function seemed to differ from other languages. Hardly uncommon given the "delicate" nature of encryption. What grabbed my attention was that the results from ColdFusion and Java were also different. Not what I would have expected.

<cfset myText        = "guess this!1 2 3" />
<cfset myKey         = generateSecretKey("Blowfish", 32) />
<cfset cfEncrypted   = Encrypt(myText, myKey, "Blowfish/ECB/NoPadding", "Hex") />

<!--- results (using 

tags because the blogger eats line breaks!) ---> <strong>ColdFusion:</strong><hr /> <cfoutput> <p><strong>MyText</strong> : #myText#</p> <p><strong>MyKey</strong> : #myKey#</p> <p><strong>CF Encrypted</strong> : #cfEncrypted#</p> </cfoutput>



Java
<!---  generate a secret key --->
<cfset SecretKeySpec     = createObject("java", "javax.crypto.spec.SecretKeySpec") />
<cfset myKeyBytes         = CharsetDecode(myKey, "utf8") />
<cfset keySpec             = SecretKeySpec.init(myKeyBytes, "Blowfish") />
<!--- get a cipher instance for encrypting --->
<cfset Cipher             = createObject("java", "javax.crypto.Cipher") />
<cfset encryptor         = Cipher.getInstance("Blowfish/ECB/NoPadding") />
<cfset encryptor.init(Cipher.ENCRYPT_MODE, keySpec) />
<!--- do the encryption --->
<cfset myTextBytes         = CharsetDecode(myText, "utf8") />
<cfset javaEncrypted     = encryptor.doFinal(myTextBytes) />

<!--- results --->
<cfoutput>
<p><strong>Java Encrypted</strong>  : #BinaryEncode(javaEncrypted, "hex")# </p>
</cfoutput>

Sample Results:


Since I am definitely not an expert on encryption, I just figured I was missing something. Eventually I did get the CF and java results to match by converting the secret key to binary and then back to base64.

<cfset myText        = "guess this!1 2 3" />
<cfset myKey         = generateSecretKey("Blowfish", 32) />
<cfset magicKey      = BinaryEncode(CharsetDecode(myKey, "utf8"), "base64") />
<cfset cfEncrypted   = Encrypt(myText, magicKey, "Blowfish/ECB/NoPadding", "Hex") />

The new results clearly showed my magicKey now matched the java keySpec value. So of course the encrypted strings also matched.


But I am not sure why that the extra step necessary. Supposedly the value returned from GenerateSecretKey() is already base64 encoded. Also, it seems to work correctly in reverse (ie when the secret key is generated from the javax.crypto.KeyGenerator).

So am I missing something here, or is this a "feature"?


Update: Yep! I was obviously staring at this for too long. I missed something obvious. I should have used BinaryDecode(myKey, "base64") to get the java key bytes and vice versa. Now it make sense! ;)

As mentioned in the comments, using CBC mode is a bit different. As it requires an IV (Initialization Vector). Here is a basic example using CBC mode. Though in practice, you probably would not use the same IV each time as I am doing here. I also included a lot of extra debugging output. So you can verify the values used by both ColdFusion and Java are the same.

<!---
    ENCRYPT WITH COLDFUSION
--->
<cfset algorithm      = "Blowfish/CBC/PKCS5Padding">
<cfset myText        = "can you guess this?" />
<cfset myKey         = generateSecretKey("Blowfish", 32) >
<cfset myIV             = CharsetDecode("12345678", "utf8")>
<cfset cfEncrypted   = Encrypt(myText, myKey, algorithm, "base64", myIV) />

<!--- results (using <p> tags because the blogger eats line breaks!) --->
<strong>ColdFusion (Encrypted):</strong><hr />
<cfoutput>
    <p><strong>MyText</strong> : #myText#</p> 
    <p><strong>MyKey</strong> : #myKey#</p>  
    <p><strong>MyIV</strong> : <cfdump var="#myIV#"></p>  
    <p><strong>CF Encrypted</strong> : #cfEncrypted#</p> 
</cfoutput>
</p>

<!---
    ENCRYPT WITH JAVA
--->
<cfset SecretKeySpec       = createObject("java", "javax.crypto.spec.SecretKeySpec") />
<cfset myKeyBytes          = BinaryDecode(myKey, "base64") />
<cfset keySpec             = SecretKeySpec.init(myKeyBytes, "Blowfish") />
<cfset ivSpec               = createObject("java", "javax.crypto.spec.IvParameterSpec").init( myIV )>
<cfset Cipher              = createObject("java", "javax.crypto.Cipher") />
<cfset encryptor           = Cipher.getInstance( algorithm ) />
<cfset encryptor.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec) />
<cfset myTextBytes         = CharsetDecode(myText, "utf8") />
<!--- convert encrypted byte array to base64 string --->
<cfset javaEncrypted       = BinaryEncode( encryptor.doFinal(myTextBytes), "base64") />

<!--- results (using <p> tags because the blogger eats line breaks!) --->
<strong>Java Encrypted:</strong><hr />
<cfoutput>
    <p><strong>myTextBytes (utf8)</strong> : #CharsetEncode(myTextBytes, "utf8")#</p>  
    <p><strong>keySpec</strong> : #BinaryEncode(keySpec.getEncoded(), "base64")#</p> 
    <p><strong>ivSpec (raw)</strong> : <cfdump var="#ivSpec.getIV()#"></p>  
    <p><strong>ivSpec (utf8)</strong> : #CharsetEncode(ivSpec.getIV(), "utf8")#</p>  
    <p><strong>Java Encrypted</strong>  : #javaEncrypted# </p>
</cfoutput>

Now just to prove the results are compatible, you can decrypt the ColdFusion results with Java (and vice versa).

<!---
    DECRYPT *COLDFUSION* RESULT WITH JAVA
--->
<cfset SecretKeySpec     = createObject("java", "javax.crypto.spec.SecretKeySpec") />
<cfset myKeyBytes        = BinaryDecode(myKey, "base64") />
<cfset keySpec           = SecretKeySpec.init(myKeyBytes, "Blowfish") />
<cfset ivSpec             = createObject("java", "javax.crypto.spec.IvParameterSpec").init( myIV )>
<cfset Cipher            = createObject("java", "javax.crypto.Cipher") />
<cfset decryptor         = Cipher.getInstance( algorithm ) />
<cfset decryptor.init(Cipher.DECRYPT_MODE, keySpec, ivSpec) />
<!--- convert our encrypted (base64 encoded) string to a byte array --->
<cfset encryptedBytes     = BinaryDecode( cfEncrypted , "base64") />
<!--- the decrypted bytes will be in UTF8 --->
<cfset javaDecrypted      = CharsetEncode( decryptor.doFinal( encryptedBytes ), "utf8") />

<cfoutput>
    <p><strong>Java Decrypted</strong>  : #javaDecrypted# </p>
</cfoutput>

<!---
    DECRYPT *JAVA* RESULT WITH COLDFUSION
--->
<!--- decrypt (base64 encoded) encrypted string --->
<cfset cfDecrypted   = Decrypt(javaEncrypted, myKey, algorithm, "base64", myIV) />

<cfoutput>
    <p><strong>ColdFusion Decrypted</strong>  : #cfDecrypted# </p>
</cfoutput>

...Read More

Thursday, November 19, 2009

Seeing Stripes: Experiment with ZXing

I recently took a look at the ZXing Project which is described as "an open-source, multi-format 1D/2D barcode image processing library implemented in Java.". While its primary focus is mobile phones, I decided to look at the javase component of the project. ZXing also comes with a servlet. But I first wanted to see how the lower level components worked.


Setup
Not seeing any pre-compiled jars, I downloaded the latest sources (ZXing-1.4.zip). After loading it into Eclipse, and poked around a bit to figure out how to decode barcode images of different types. Finally, I used the pre-made ant files to create the two jars I needed: core.jar and javase.jar.

Decoding barcode images
While the project has some decent documentation, the developer notes seem to be outdated in a few places. But examining some of the javase classes turned up a convenience class called MultiFormatReader. From what I could tell it provides a simple way to decode barcodes when you may not know the type ahead of time. Obviously if you know the barcode type ahead of time, it is more efficient to use the specific reader for that type. But since I wanted to test all supported types the MultiFormatReader was perfect for my needs.

Now before I could test ZXing, I needed some barcode images. So I used CFBarbecue.cfc to generate a sample of all of the barcodes supported by the Barbecue library. Now ZXing does not support all of them. But there is a good intersection between the two. Once I had my images, I created an instance of MultiFormatReader, using the ever-handy JavaLoader.cfc. I also passed the reader some hints to increase accuracy.

Note: This code is for demonstration purposes only. Normally, the javaLoader should be stored in the server scope to avoid memory leaks.

<cfset paths = [] />
<cfset arrayAppend(paths, expandPath('zxing-1.4/core/core.jar')) />
<cfset arrayAppend(paths, expandPath('zxing-1.4/javase/javase.jar')) />
<cfset loader = createObject("component", "javaloader.JavaLoader").init( paths ) />

<!--- Create hints for the reader. TRY_HARDER increases accuracy --->
<cfset DecodeHintType = loader.create("com.google.zxing.DecodeHintType") />
<cfset hints = loader.create("java.util.Hashtable").init() />
<cfset hints.put(DecodeHintType.TRY_HARDER, javacast("boolean", true)) />

<!--- Since we will be reading multiple images, create one reader and reuse it  --->
<cfset Reader = loader.create("com.google.zxing.MultiFormatReader").init() />
<cfset Reader.setHints(hints) />

Next, I used cfdirectory to grab a list of all of my barcode images. Then looped through the images and attempted to decode each one. Now in order to use the reader's decode method, I needed to convert my image to a compatible format: BinaryBitmap. So I read in each image, extracted the underlying BufferedImage and passed it through a few of the ZXing classes designed to prepare images for decoding.

Finally, I called the reader's decode method to decode the image. If the image was successfully decoded it returns a Result object containing some useful properties like the raw text and barcode type.

One thing to be aware of is that error handling is unlikely to yield any useful information, beyond the fact that an error occurred. From the notes in the current source code, the way in which decoding errors are handled is undergoing a transformation, to increase performance. So for now do not expect much exception information beyond name, rank and serial number.


<!--- read all barcode images in my subdirectory --->
<cfdirectory action="list" directory="#ExpandPath('images')#"
            filter="*.gif"
            name="barcodeImages" />

<!---
    Try and decode each barcode with z-xing.
    Note: Not all codes are supported by z-xing!!!
--->
<cfoutput query="barcodeImages">
    <cfset imagePath = Directory &"/"& name />
    <cftry>

        <!--- display the original image --->
        <strong>Barcode Image</strong> #name# <br />
        <cfimage action="writeToBrowser" source="#imagePath#"><br />

        <!--- extract the BufferedImage of the current barcode --->
        <cfset img = ImageGetBufferedImage(ImageRead(imagePath)) />

        <!--- prepare the image for decoding --->
        <cfset source = loader.create("com.google.zxing.client.j2se.BufferedImageLuminanceSource").init(img) />
        <cfset binarizer = loader.create("com.google.zxing.common.GlobalHistogramBinarizer").init(source) />
        <cfset bitmap = loader.create("com.google.zxing.BinaryBitmap").init(binarizer) />

        <!--- This method reuses the existing settings for decoding multiple images --->
        <cfset decodedResult = Reader.decodeWithState(bitmap) />

        <!--- display the results --->
        <strong>Decoded Text</strong> #decodedResult.getText()# <br />
        <strong>Decoded Format</strong> #decodedResult.getBarcodeFormat()# <br />

        <cfcatch>
            Not supported or unable to decode: #cfcatch.type# #cfcatch.message#
        </cfcatch>
    </cftry>
    <br /><br /><hr />
</cfoutput>




All of my sample images were decoded except:

  • 2of7
  • Codabar
  • Int2of5
  • Monarch
  • NW7
  • PDF417
  • PostNet
  • Std2of5
  • USD4
While I knew some of them are not supported, but I was surprised that the PD417 and Interleaved 2 of 5 samples failed. I am not yet sure why. For now I will assume it was an error on my part. I will have to investigate the individual readers further.

However, I was able to decode a DataMatrix barcode using the DataMatrixReader. (Created using the free servlet at IDAutomation.com). Just be aware the current project documentation lists DataMatrix decoding as "alpha quality". So do not expect miracles.




<cfset imagePath = "c:\barCode_loremIpsum_idAutomation.gif" />

<cfset DecodeHintType = loader.create("com.google.zxing.DecodeHintType") />
<cfset hints = loader.create("java.util.Hashtable").init() />
<cfset hints.put(DecodeHintType.PURE_BARCODE, javacast("boolean", true)) />

<!--- prepare the image for decoding --->
<cfset img = ImageGetBufferedImage(ImageNew(imagePath)) />
<cfset source = loader.create("com.google.zxing.client.j2se.BufferedImageLuminanceSource").init(img) />
<cfset binarizer = loader.create("com.google.zxing.common.GlobalHistogramBinarizer").init(source) />
<cfset bitmap = loader.create("com.google.zxing.BinaryBitmap").init(binarizer) />

<!--- attempt to decode the image --->
<cfset Reader = loader.create("com.google.zxing.datamatrix.DataMatrixReader") />
<cfset decodedResult = Reader.decode( bitmap, hints ) />

<cfoutput>
<!--- display the original image --->
<strong>Barcode Image</strong> #imagePath# <br />
<cfimage action="writeToBrowser" source="#imagePath#"><br />

<!--- display the results --->
<strong>Decoded Result</strong><br />
#decodedResult.getText()# <hr />
</cfoutput>

Other Classes
The j2se component also has a few classes you might find useful for quick tests. The first is GUIRunner. It is a very rudimentary GUI you can use to test a single bar code image. To use it, simply open a command prompt and enter the following. Just modify the "-cp" value (classpath) to point to the location of your jars.

Example:


C:\test\zxing> java -cp c:\test\zxing\core.jar;c:\test\zxing\javase.jar com.google.zxing.client.j2se.GUIRunner


The other class is CommandLineRunner. It can be used to decode a single image, a directory of images or a url. Just supply the locations of your jars and the file/directory/url you wish to decode. The "--try_harder" hint can be used to increase accuracy, and the "--dump_results" option to save the results to a file. Note: The results of each barcode are saved to individual files. So keep that in mind if you are decoding an entire directory.

Example:

C:\test\zxing> java -cp c:\test\zxing\core.jar;c:\test\zxing\javase.jar com.google.zxing.client.j2se.CommandLineRunner c:/test/zxing/barcodeimages --try_harder


Well, that is it for now. I will have to experiment with some lower quality images, to see how the results stack up in a real test. (Once I find a supported phone ...)

Wednesday, November 18, 2009

SOT: Who is the fairest browser of them all?

I have long been a fan of Firefox. But somewhere in between versions 2 and 3.5 it started behaving like a black-hole of resources, sucking up more and more memory, growing increasing sluggish. Not to mention taking forever to start up. So fickle user that I am, I have been trying out other browsers while I give Firefox one last chance before giving it the boot.


Update November 19, 2009: I am now experimenting with the Firefox 3.6b3 beta and also a plugin called AFOM 2.0 for memory management. (Note: I do not know if this plugin officially supports 3.6b3 yet). But the results from my limited testing seem very promising. (Windows only)


Update November 27, 2009: While the plugin helps, there are still issues. I think it is time to declare defeat. The memory problem is definitely Firefox!

...Read More

Monday, November 16, 2009

CFBarbecue.cfc (...Just because)

While experimenting with the Barbecue library last week, I put together a cfc for my testing. (You knew that was coming. It is the old developer story: read, test, build something, re-test). Anyway, I decided to post it for anyone else out there experimenting with Barbecue. The cfc can generate any of the 25+ barcodes Barbecue handles, with a full range of settings. (Rather than repeat the same code 25+ times, the cfc uses reflection)


November 19, 2009: Updated source to include missing lib\jdom.jar. Note: This jar is only needed if you are using the JavaLoader and want SVG functionality.

...Read More

Sunday, November 15, 2009

The Wrong Way to Retrieve the Last Record ID Inserted

Hardly a week goes by without seeing some forum post that either uses or recommends my pet peeve: the erroneous SELECT MAX(id) query. Mind you this is across the board, not just in CF. For some bizarre reason, this faulty technique never seems to die. And it REALLY should. There are so many correct alternatives, I cannot understand why anyone would still use it for new code. If you are even considering it, might I inject a note of sanity and recommend some excellent reading material on Adrian J. Moreno's blog:


(His comments on the topic are much more educational and polite than mine ;-)

Friday, November 13, 2009

Everything CFDBINFO Never Told You About Your Database (Connection Metadata)

I was doing some debugging and came across the nifty java.sql.DatbaseMetaData class. It is an interface implemented by the various database drivers and it provides you with a ton of information about both your database and driver capabilities. While you can glean some of the information from <cfdbinfo>, DatbaseMetaData tells you so much more.


  • What database or driver version you are using?
  • What is the maximum number of concurrent connections?
  • What is the maximum number of tables in an ORDER BY?
  • What happens when you concatenate a NULL value with a non-NULL value?


Apparently, the DatabaseMetaData class can tell you that. While I would not take all of these values "as gospel", they do provide some solid guidelines.
<cfscript>
myDSN = "MSSQL_2005";

// open a connection to the datasource
factory = createObject("java", "coldfusion.server.ServiceFactory");
datasource = factory.getDataSourceService().getDataSource( myDSN );
connection = datasource.getConnection();
// extract the database metadata
metadata = connection.getMetaData();

results = {};

// Display version information
results["getJDBCMajorVersion()"] = metadata.getJDBCMajorVersion();
results["getJDBCMinorVersion()"] = metadata.getJDBCMinorVersion();
results["getDriverMinorVersion()"] = metadata.getDriverMinorVersion();
results["getDriverName()"] = metadata.getDriverName();
results["getDriverVersion()"] = metadata.getDriverVersion();
results["getDatabaseMajorVersion()"] = metadata.getDatabaseMajorVersion();
results["getDatabaseMinorVersion()"] = metadata.getDatabaseMinorVersion();
results["getDatabaseProductName()"] = metadata.getDatabaseProductName();
results["getDatabaseProductVersion()"] = metadata.getDatabaseProductVersion();

// cleanup
connection.close();
</cfscript>

<cfdump var="#results#" label="[#myDSN#] java.sql.DatbaseMetaData" />
As there are so many functions, I used a bit of reflection to quickly dump all of the simple properties into a structure. (See the full code at the end of the entry). It was interesting to see how the metadata differed across drivers. Both in what information was exposed and the results of the core methods. Notice the sql keywords differ when using the JTDS driver and the built-in CF8 driver.

Note: Unfortunately, getSQLKeywords() only returns potential keywords that are not also ansi sql:2003 reserved words. But with a little effort you could easily create a function to list of all potential keywords.

CF8 Driver

JTDS Driver


The metadata contains all kinds of interesting information. So if you ever wondered about your database or driver capabilities, read up on java.sql.DatbaseMetaData. What it reveals could surprise you.

Full Code

<cfscript>
myDSN = "MSSQL_2005";

// open a connection to the datasource
factory = createObject("java", "coldfusion.server.ServiceFactory");
datasource = factory.getDataSourceService().getDataSource( myDSN );
connection = datasource.getConnection();
// extract the database metadata
connMetadata = connection.getMetaData();

// get all methods in the metadata class
allMethods = connMetadata.getClass().getDeclaredMethods();
Modifier = createObject("java", "java.lang.reflect.Modifier");

results = {};

// Execute all simple methods and store the results
for(x=1; x <= arrayLen(allMethods); x++)
{
method = allMethods[x];

// Get the parameters for the current method
parameters = method.getParameterTypes();

// If this is a simple method, requiring no extra information ..
if (Modifier.isPublic(method.getModifiers()))
{
if (not arrayLen(parameters) AND method.getName() neq "getConnection")
{
try
{
// run the current method
data = method.invoke( connMetadata, parameters );
// convert resultsets to a query object first
if ( isInstanceOf(data, "java.sql.ResultSet")) {
data = createObject("java", "coldfusion.sql.QueryTable").init(data);
}
results[method.getName()] = data;
}
catch(java.lang.Exception e)
{
results[method.getName()] = e.message;
}
}
}
}

connection.close() ;
</cfscript>

<cfdump var="#results#" label="connection.getMetaData() " />

...Read More

Thursday, November 12, 2009

MS SQL: Useless Function Trivia

While reviewing some MS SQL 2005 metadata, I stumbled across something I never noticed before.

 
<cfquery name="q" datasource="MSSQL_2005">
SELECT
{fn CurDate()} AS [CurrentDate],
{fn CurTime()} AS [CurrentTime],
{fn DayName( getDate() )} AS [DayName],
{fn DayOfMonth( getDate() )} AS [DayOfMonth],
{fn DayOfWeek (getDate() )} AS [DayOfWeek],
{fn DayOfYear(getDate() )} AS [DayOfYear],
{fn Extract(Day From getDate())} AS [ExtractDay],
{fn Hour(getDate() )} AS [Hour],
{fn Minute(getDate() )} AS [Minute],
{fn Month(getDate() )} AS [Month],
{fn MonthName(getDate() )} AS [MonthName],
{fn Now()} AS [Now],
{fn Quarter(getDate() )} AS [Quarter],
{fn Second(getDate() )} AS [Second],
{fn Week(getDate() )} AS [Week],
{fn Year(getDate() )} AS [Year],
{fn TimestampAdd(SQL_TSI_DAY, 14, getDate())} AS [TimestampAdd],
{fn TimestampDiff(SQL_TSI_DAY, '2009-10-31', getDate())} AS [TimestampDiff]
</cfquery>
<cfdump var="#q#" label="Old MS SQL Functions fn{}" />

...Read More

OT: Lame error messages

So I was having fun, trying to break things, and came across a lame excuse only a computer could get away with:

com.mysql.jdbc.exceptions.MySQLSyntaxErrorException: memory exhausted.

I can see it now: "Sorry Boss-man. No can do. Geez, my memory is exhausted. I need time to relax, recharge, reboot. Check back with me tomorrow."

Sunday, November 8, 2009

ColdFusion: Introduction to the Java Barbecue Library (For Beginners)

Just to get this out of the way: if you are looking for a CF wrapper for the Barbecue library, this is not the entry for you. If you are interested in the basics of using Barbecue, feel free to read on.

Why write yet another entry on Barbecue? Well, I was recently reading about barcodes, for my own edification, and did not find many introductory resources (you know ... ones that ask and answer those silly novice questions?). So decided to write up my findings and first impressions, in the hopes of providing someone else with a starting point in their research. Even if they are not using CF.

Background on Barcodes
Obviously using the library requires you know at least a little bit about the different bar code types. For example, some encode numeric digits only, others have different limits as to how many characters they can encode, etcetera. Two of the more helpful references I came across were: Different Types of Barcodes and a high level Barcode Comparison Chart.

Installing the Barbecue Library
Using the Barbecue library is very simple. Just download the library from sourceforge.net and extract the main jar from the .zip file. The current jar version is: barbecue-1.5-beta1.jar. Then place the main jar somewhere in your CF classpath, such as: C:\ColdFusion8\wwwroot\WEB-INF\lib. Finally restart the ColdFusion server so it will detect the jar.

Another option is to use Mark Mandel's JavaLoader.cfc, which is the option I chose. Just load the Barbecue jar in your array of paths, and create a new instance of the JavaLoader. (Obviously, this is just for demonstration. Usually you store the javaLoader instance in the server scope to avoid memory leaks).

Note: The jdom.jar must also be added to the JavaLoader paths if you intend to use Barbecue's SVG functionality.

<cfset paths = [] />
<cfset arrayAppend(paths, expandPath('barbecue-1.5-beta1/barbecue-1.5-beta1.jar')) />
<cfset arrayAppend(paths, expandPath('barbecue-1.5-beta1/lib/jdom.jar')) />
<cfset loader = createObject("component", "javaloader.JavaLoader").init( paths ) />

Factory Workers: (So much for progress..)
I found the easiest way to work with bar codes was using the factory classes. The factories provide easy to use static methods that make it a breeze to create, format and save barcodes.


Creating a Barcode
To a create a barcode, you use the BarcodeFactory. This class contains about 25+ methods for creating several different types of barcodes, such as Interleaved 2 or 5, EAN128, etcetera. Currently, only one 2d bacode is supported: PDF417. The methods are intuitively named, and most require only the string to be encoded. For example, to create a Bookland barcode, just grab a reference to the Factory and call the createBookland() method with the value you wish to encode:

<cfset factory = loader.create("net.sourceforge.barbecue.BarcodeFactory") />
<cfset barcode = factory.createBookland( "0123456789" ) />

Creating a Barcode Image (Simple)

Now the object returned from the BarCodeFactory is not image. It is an instance of one of the Barbecue classes: net.sourceforge.barbecue.Barcode. But there are several easy ways for converting the barcode object into an image. I found at least five (5) methods. (There is a catch to a few of them, but more about that in a minute) .

The first three (3) options use the BarcodeImageHandler factory. It has a few handy methods for converting a barcode object to either an in memory image or to a physical file.

(1) Generating a BufferedImage
To convert a barcode object to a BufferedImage, simply grab a reference to the factory, then call the getImage() method. To display or manipulate the BufferedImage in CF, just wrap it in a CF image object.
<cfset ImageHandler = loader.create("net.sourceforge.barbecue.BarcodeImageHandler") />
<cfset buffImage = ImageHandler.getImage( barcode ) />
<cfset barcodeImage = ImageNew(buffImage) />
<cfimage action="writeToBrowser" source="#barcodeImage#" />
(2) Writing Images to an OutputStream
You can also write the image directly to an OutputStream. There are various uses of OuputStream's, such as streaming images to the browser. There is probably less need for that particular technique in later versions of CF. But as there are several different types of streams, it is good to be aware of this option.

The image handler has three (3) methods for writing images to a stream: writePNG(), writeGIF() and writeJPG(). As their names imply, they generate an image in the desired format and write the image bytes to the supplied stream. You can then use the stream however you wish:

<cfset ImageHandler = loader.create("net.sourceforge.barbecue.BarcodeImageHandler") />
<cfset outStream = loader.create("java.io.ByteArrayOutputStream").init() />
<cfset ImageHandler.writePNG( barcode, outStream ) />
<!--- 1) Display the image using cfimage ... --->
<cfset barcodeImage = ImageNew(outStream.toByteArray()) />
<cfimage action="writeToBrowser" source="#barcodeImage#" />

<!--- 2) OR .. using cfcontent ... --->
<cfcontent type="image/png" variable="#outStream.toByteArray()#" />

(3) Saving Images to a File
Lastly, the handler has three methods for saving barcode images to a physical file: saveGIF(), saveJPG() and savePNG(). It is much like the previous option, only you pass in a java.io.File object representing the output file.

<cfset ImageHandler = loader.create("net.sourceforge.barbecue.BarcodeImageHandler") />
<cfset IOFile = loader.create("java.io.File") />

<!--- save the barcode in three (3) different formats  --->
<cfset imageFile = IOFile.init( ExpandPath("myBarcode.gif") ) />
<cfset ImageHandler.saveGIF( barcode, imageFile ) />
<img src="myBarcode.gif" alt="GIF  Format" />

Now earlier I mentioned a catch. In my tests, the generated images all had a black border along the bottom of the barcode. I am guessing it has something to do with the canvas color of the image object. One way I was able to remove it, was by creating a blank image first. So I could control the canvas color. Then use some lower level methods to draw the barcode onto my image. That brings me to the other two (2) options for creating images.




Creating a Barcode Image (Advanced)

While not as easy as using the factory methods, you can also use two methods within the Barcode class itself to generate an image. But unlike using the image factory, it is strictly B.Y.O. (bring your own) .. image. You must already have a blank image in order to use these methods. They are probably what the BarcodeImageFactory uses behind the scenes. However, I thought I would point them out to explain how they work.

1) Using Barcode.draw(graphics, x, y)
The first, and simpler, method is: draw(). To use it, I just created a blank CF image. Since the default canvas color for "rgb" images is black, I changed it to white. Then extracted the graphics object from my image and passed it into the draw function. The result was a barcode without that bottom border.




<!--- Option 1: RBG image, white canvas --->
<cfset img = ImageNew("", barcode.getWidth(), barcode.getHeight(), "rgb", "ffffff") />
<cfset graphics = ImageGetBufferedImage(img).getGraphics() />
<cfset barcode.draw( graphics, 0, 0) />

<cfimage action="writeToBrowser" source="#img#">
<br /><br />

<!--- Option 2: Transparent image --->
<cfset img = ImageNew("", barcode.getWidth(), barcode.getHeight(), "argb") />
<cfset graphics = ImageGetBufferedImage(img).getGraphics() />
<cfset barcode.draw( graphics, 0, 0) />

<div style="background-color: blue;">
<cfimage action="writeToBrowser" source="#img#"> <br />
</div>

2) Using Barcode.output(output)
The second method is a little more complex. You probably will not need it. But still, it is good to know. The output() method works with an instance of what Barbecue calls an Output object. The are several types of Output classes, and they are all just used to convert Barcode objects from one format to another: like to image, svg or eps.

Since I wanted to create an image, I used the GraphicsOutput class. It is not as complicated as it looks, but it does involve more moving pieces than other options. Constructing a GraphicsOutput object requires several pieces of information: the image graphics, barcode font and fore/background colors. You simply pass these objects into the constructor to create a new Output object. Then call Barcode.output() to paint the barcode onto your image graphics using the supplied settings.

Example

Code

<!--- create a new image and extract the graphics --->
<cfset img = ImageNew("", barcode.getWidth(), barcode.getHeight(), "rgb", "ffffff") />
<cfset graphics = ImageGetBufferedImage(img).getGraphics() />

<!--- create the desired font and colors --->
<cfset Font = createObject("java", "java.awt.Font") />
<cfset Color = createObject("java", "java.awt.Color") />

<cfset barcodeFont = Font.init("Arial", Font.PLAIN, 12 ) />
<cfset backgroundColor = Color.BLUE />
<cfset foregroundColor = Color.decode("##008040") />

<!--- grab a Color object that will be used to set the barcode fore/background --->
<cfset GraphicsOutput = loader.create("net.sourceforge.barbecue.output.GraphicsOutput")>
<cfset gOut = GraphicsOutput.init( graphics, barcodeFont, foregroundColor, backgroundColor ) />
<cfset barcode.output( gOut ) />

<cfset ImageWrite( img, ExpandPath("ColdFusion_Barbecue_GraphicsOutput.png")) />
<!--- display the barcode image --->
<cfimage action="writeToBrowser" source="#img#"> <br />


Working with Barcode Properties

Working with the various ways of creating images eventually brought me back to the Barcode class itself, and using its methods to alter some of the default settings. One of the things that struck me about the Barcode class is that it extends JComponent. At first that seemed a bit strange, as JComponent is one of the base classes for swing gui components. But further reading revealed Barbecue can be used in servlets or swing applications. So extending JComponent made more sense.

Now I mentioned swing for a reason. After testing various Barcode properties, I quickly realized many of the properties only apply if you are using swing. For example, there are several methods for adjusting width and height. But they had no effect when using images. Considering these methods are inherited from JComponent, that makes sense. (Not that you can really force the barcode dimensions anyway). An entry on Adam Presley's blog confirmed my suspicion. You cannot set the dimensions explicitly. But instead should use the barWidth and barHeight properties to adjust the overall barcode size.

Here are some of the more useful Barcode properties.

Barcode.setBarWidth()/setBarHeight()
These methods can be used to set the width of the thinnest bar or the height of a single bar, within the overall barcode. You may need to experiment to get the correct settings. So you do not end up with an invalid barcode or one that does not render properly ;)

setBarWidth( 6 )


Code

<cfset BarcodeFactory = loader.create("net.sourceforge.barbecue.BarcodeFactory") />
<cfset barcode = BarcodeFactory.createStd2of5( "0123456789" ) />
<cfset barcode.setBarWidth( 6 ) />
...

Barcode.setDrawingQuietSection(boolean)
This setting controls whether extra whitespace (ie quiet zone) should be rendered.

Code

	<cfset barcode.setDrawingQuietSection( false ) />

setDrawingQuietSection(true)

setDrawingQuietSection(false)


Barcode.setDrawingText(boolean)
This setting controls whether the barcode data (ie un-encoded) is displayed beneath the barcode.

Code

	<cfset barcode.setDrawingText( false ) />
setDrawingText(false)


Barcode.setLabel(string)
This method should allow you to override the text displayed beneath the barcode. But I was not able to get this setting to work.

Barcode.setFont(java.awt.Font)
This method allows you to use a different font for any text displayed beneath the barcode.

Code:

<cfset Font = createObject("java", "java.awt.Font") />
<cfset barcodeFont = Font.init("Arial", Font.ITALIC, 12) />
<cfset barcode.setFont( barcodeFont ) />
setFont( [Arial, Italic, size 12] )



Barcode.setBackground(java.awt.Color)/setForeground(java.awt.Color)
These methods can be used to change the foreground or background colors of the barcode. They are inherited from JComponent, but are used by the Barcode class.

Code:

<!--- change foreground --->
<cfset Color = createObject("java", "java.awt.Color") />
<cfset barcode.setForeground( Color.RED ) />

<!--- change background (hex color) --->
<cfset Color = createObject("java", "java.awt.Color") />
<cfset barcode.setBackground( Color.decode("##0080c0") ) />

setForeground()


setBackground()


Barcode.setResolution(int)
Used to adjust the resolution. Default is 72dpi.

Code:
	<cfset barcode.setResolution( 300 ) />
SVG
You can also convert barcode objects to svg. Simply grab a reference to the SVGFormatter class, then call the formatAsSVG() method. The result is an svg string.

Code:
<cfset BarcodeFactory = loader.create("net.sourceforge.barbecue.BarcodeFactory") />
<cfset barcode = BarcodeFactory.createBookland( "0123456789" ) />
<cfset SVGFormatter = loader.create("net.sourceforge.barbecue.formatter.SVGFormatter") />
<cfset svgString = SVGFormatter.formatAsSVG( barcode )>
Related Entries:
CFBarbecue.cfc (...Just because)
Seeing Stripes: Experiment with ZXing


Saturday, November 7, 2009

OT: ColdFusion Trials (Let's Do The Time Warp Again)

I was checking my "trials" email folder the other day and these messages gave me a chuckle. That and the fact that I received a "Your ColdFusion 9 trial starts now" message, before my download had even finished! Can you spot what is wrong here?


Yes... I did download the ColdFusion 9 Trial previously. But it still made me laugh ;)

ColdFusion: Small Try/Catch Gotcha With CreateObject() and Exceptions

Exceptions can be deceptive and loathsome creatures at times. Case in point, I was happily using createObject() to load an object from a java jar in my classpath. I then added a try/catch. So I could detect when the jar probably was not added to the CF classpath properly, and display a more meaningful error message.


<cfscript>
try
{
handler = createObject("java", "net.sourceforge.barbecue.BarcodeImageHandler");
}
catch(java.lang.ClassNotFoundException e)
{
throwError(message="Verify the Barbecue jar is in your CF classpath", type="SupportingClassNotFound");
}
</cfscript>

Great in theory, the only problem was my try/catch did not work. I double checked the stack trace and it sure seemed like I was catching the correct exception type: java.lang.ClassNotFoundException. So I tried the broader type java.lang.Exception. Then cfdump'd the details, to see if I could spot the problem. Sure enough, the actual exception type was not what CF was reporting on the error screen. The actual exception type being:
coldfusion.runtime.java.JavaObjectClassNotFoundException.


(Boy, what a mouthful. As if the base exception name is not long enough already...?)


Apparently the stack trace in the error page was displaying the RootCause, not the actual exception thrown. While that makes some sense, unfortunately the thrown exception type is what is needed to catch the darned thing. Anyway, once I changed my try/catch to use the actual exception type being thrown, everything was just dandy.
<cfscript>
try
{
handler = createObject("java", "net.sourceforge.barbecue.BarcodeImageHandler");
}
catch(coldfusion.runtime.java.JavaObjectClassNotFoundException e)
{
throwError(message="Verify the Barbecue jar is in your CF classpath", type="SupportingClassNotFound");
}
</cfscript>
What is the world coming to when you cannot even trust error messages Oh, wait. Scratch that. Error messages lie all the time. I am sure they do not mean to lie, they just hold back critical information when you most need it. The secretive little trolls ;)

...Read More

No more use for StructFind() ?

For some reason I always forget about the one good use for StructFind(). (Thank goodness for old blog entries). Though it seems even that use is gone in ColdFusion 9. Can anyone think of another one?


<!--- Works in CF9, not CF8 --->
<cfoutput>
#getSomeStructure()["dog"]#
</cfoutput>

<cffunction name="getSomeStructure" returntype="struct">
<cfset var myStruct = {} />
<cfset myStruct.cat = "felix" />
<cfset myStruct.dog = "clifford" />
<cfreturn myStruct />
</cffunction>

Tuesday, November 3, 2009

A Better PdfPageEventHandler with JavaLoader/CFCDynamicProxy

Yesterday , I wrote about discovering a true gem in latest version of Mark Mandel's JavaLoader: the CFCDynamicProxy. This tiny, but powerful, class acts as a wrapper around a ColdFusion cfc. Essentially allowing it to mimic a native java object and communicate directly with other java objects in ways it never could before! We all know just about everything in ColdFusion boils down to a java object internally. But until now there were certain things you just could not do from a cfc without taking the r-e-a-l-l-y long way around. Well, the CFCDynamicProxy changes all that.



<cffunction name="onEndPage" access="public" returntype="void" output="true"
hint="Called when a page is finished, just before being written to the document.">
<cfargument name="writer" type="any" required="true" hint="Writer for the target pdf. Instance of com.lowagie.text.pdf.PdfWriter" />
<cfargument name="document" type="any" required="true" hint="Document for target pdf. Instance of com.lowagie.text.Document" />
<cfset var Local = {} />

<cfscript>
if (len(variables.instance.footerText))
{
Local.cb = arguments.writer.getDirectContent();
Local.cb.saveState();

Local.cb.beginText();
Local.cb.setColorFill( variables.instance.textColor );
Local.cb.setFontAndSize( variables.instance.font, javacast("float", variables.instance.fontSize) );
Local.cb.setTextMatrix( arguments.document.left(), arguments.document.bottom() - 10);
Local.text = variables.instance.footerText &" page ["& arguments.writer.getPageNumber() &"]";
Local.cb.showText( Local.text );
Local.cb.endText();

Local.cb.restoreState();
}
</cfscript>

</cffunction>


Having completed my faux-java-class/cfc, the next step was using the CFCDynamicProxy to see if this stuff actually worked. Using the Dynamic proxy is like using any other jar with the JavaLoader. You just add the new cfcdynamicproxy.jar to the array of paths. Then create an instance of the JavaLoader, except this time you set the loadColdFusionClassPath parameter equal to true. This allows the JavaLoader to access ColdFusion's classes to create the proxy object.

...Read More

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep