Tuesday, April 27, 2010

ColdFusion: Verify SMTP, POP3 and IMAP Mail Server Connection

I saw an intriguing question about mail servers this week, on stackoverflow.com. The question was is it possible to verify an SMTP mail server connection programatically? Essentially duplicate the "Verify mail server connection" functionality that exists in the CF Administrator. Though I am certain this is old news to some of you, the answer is yes.

Interestingly, both suggestions on stackoverflow.com utilized java libraries already built into all three engines: Adobe ColdFusion, Railo and OpenBlueDragon. Member folone suggested using the SMTPClient class in Apache Commons Net , while member sfussenegger suggested using the core JavaMail library with an Authenticator. (Unfortunately, I do not know their full names).

Having used JavaMail and Authenticators before, I was somewhat familiar with that option. Though a bit rusty. Being less familiar with Commons Net, I had to do some research. Apparently, Commons Net is the predecessor of JavaMail. So the two libraries serve similar purposes, but Commons Net is a lower level API. However, both can be used to communicate with SMTP and POP3 servers. Though I do not think the Commons library supports IMAP.

Anyway, I was curious about the Commons classes, so I played around with them a bit. Eventually I managed to force an SSL connection with POP3, courtesy of this tip and the convenience MailSSLSocketFactory class. However, the code only works in Adobe CF. Unfortunately, Railo and OpenBlueDragon do not seem to include the MailSSLSocketFactory class, and I did not want to create my own.

<cfscript> 
   isVerified = false;
   try {
      SSLFactory = createObject("java", "com.sun.mail.util.MailSSLSocketFactory").init("SSL");
      POP3Client = createObject("java", "org.apache.commons.net.pop3.POP3Client").init();
      POP3Client.setSocketFactory( SSLFactory );
      POP3Client.connect("pop.gmail.com", 995);
      isVerified = POP3Client.login("user@gmail.com", "user password");
      WriteOutput("isVerified="& isVerified &"<br />");
      POP3Client.logout();
   }
   // error occurred during login or logout
   catch(java.io.IOException e) {
      WriteDump(e);
   }
</cfscript> 

Since JavaMail works on all three engines, with no extra classes, it seemed the best choice. Plus I really liked the idea of an all-in-one validator, not just smtp. So I put together a rough function that validates SMTP, POP3 and IMAP connections. It should be compatible with CF8, CF9, Railo and OpenBlueDragon. Tested against gmail and hMailServer.

For those not familiar with JavaMail, the function is very simple. It first stores the properties you want to use for the connection (like TLS). Then creates a new mail Session using those properties. Next it attempts to open a connection to the given mail server and requests authentication. Finally the connection is closed. If an error occurs, the function checks the exception type to determine if the credentials were invalid or it was some other type failure. The function returns a structure with three keys: WasValidated, ErrorType and ErrorDetail.

One last note about the function. When TLS is selected, it is made mandatory. So if the mail server on the other end does not support TLS, the verification will fail, for security reasons. But you can change that behavior if you wish.

If you are interested in reading more about JavaMail, these two articles are very comprehensive: jGuru: Fundamentals of the JavaMail API and JavaMail FAQ's.

Example
<cfset response = verifyMailServer(    host     = "smtp.gmail.com",
                                          protocol = "smtp",
                                          port     = 587,    
                                          user     = "username@gmail.com",
                                          password = "user password",
                                          useTLS     = true,
                                          debug    = true,
                                          overwrite = false,
                                          logPath  = "c:\verifyMailServer_Test.log"
                                   ) />
<cfdump var="#response#" label="Verfication Results">

Function
<cffunction name="verifyMailServer" returntype="struct" access="public" output="true">
    <cfargument name="protocol" type="string" required="true" hint="Mail protocol: SMTP, POP3 or IMAP" />
    <cfargument name="host" type="string" required="true" hint="Mail server name (Example: pop.gmail.com)"/>
    <cfargument name="port" type="numeric" default="-1" hint="Mail server port number. Default is -1, meaning use the default port for this protocol)" />
    <cfargument name="user" type="string" required="true" hint="Mail account username" />
    <cfargument name="password" type="string" required="true" hint="Mail account password" />
    <cfargument name="useSSL" type="boolean" default="false" hint="If true, use SSL (Secure Sockets Layer)" >
    <cfargument name="useTLS" type="boolean" default="false" hint="If true, use TLS (Transport Level Security)" >
    <cfargument name="enforceTLS" type="boolean" default="false" hint="If true, require TLS support" >
    <cfargument name="timeout" type="numeric" default="0" hint="Maximum milliseconds to wait for connection. Default is 0 (wait forever)" />
    <cfargument name="debug" type="boolean" default="false" hint="If true, enable debugging. By default information is sent to is sent to System.out." >
    <cfargument name="logPath" type="string" default="" hint="Send debugging output to this file. Absolute file path. Has no effect if debugging is disabled." >
    <cfargument name="append" type="boolean" default="true" hint="If false, the existing log file will be overwritten" >

    <cfset var status         = structNew() />
    <cfset var props         = "" />
    <cfset var mailSession     = "" />
    <cfset var store         = "" />
    <cfset var transport    = "" />
    <cfset var logFile        = "" />
    <cfset var fos             = "" />
    <cfset var ps             = "" />
    
    <!--- validate protocol --->
    <cfset arguments.protocol = lcase( trim(arguments.protocol) ) />
    <cfif not listFindNocase("pop3,smtp,imap", arguments.protocol)>
        <cfthrow type="IllegalArgument" message="Invalid protocol. Allowed values: POP3, IMAP and SMTP" />
    </cfif>
    
    <cfscript>
        // initialize status messages
        status.wasVerified     = false;
        status.errorType      = "";
        status.errorDetail  = "";

        try {
               props = createObject("java", "java.util.Properties").init();

               // enable securty settings
               if (arguments.useSSL or arguments.useTLS) {

                    // use the secure protocol
                    // this will set the property mail.{protocol}.ssl.enable = true
                    if (arguments.useSSL) {
                         arguments.protocol = arguments.protocol &"s";            
                    }
                
                    // enable identity check
                    props.put("mail."& protocol &".ssl.checkserveridentity", "true");

                    // enable transport level security and make it mandatory
                    // so the connection fails if TLS is not supported
                    if (arguments.useTLS) {
                         props.put("mail."& protocol &".starttls.required", "true");
                         props.put("mail."& protocol &".starttls.enable", "true");
                    }
               }

               // force authentication command
               props.put("mail."& protocol &".auth", "true");
            
               // for simple verifications, apply timeout to both socket connection and I/O 
               if (structKeyExists(arguments, "timeout")) {
                    props.put("mail."& protocol &".connectiontimeout", arguments.timeout);
                    props.put("mail."& protocol &".timeout", arguments.timeout);
               }

               // create a new mail session 
               mailSession = createObject("java", "javax.mail.Session").getInstance( props );

               // enable debugging
               if (arguments.debug) {
                   mailSession.setDebug( true );
                   
                   // redirect the output to the given log file
                   if ( len(trim(arguments.logPath)) ) {
                        logFile = createObject("java", "java.io.File").init( arguments.logPath );
                        fos      = createObject("java", "java.io.FileOutputStream").init( logFile, arguments.overwrite );
                        ps       = createObject("java", "java.io.PrintStream").init( fos ); 
                        mailSession.setDebugOut( ps );
                   }
               }
            
               // Connect to an SMTP server ... 
               if ( left(arguments.protocol, 4) eq "smtp") {

                    transport = mailSession.getTransport( protocol );
                    transport.connect(arguments.host, arguments.port, arguments.user, arguments.password);
                    transport.close();
                    // if we reached here, the credentials should be verified
                    status.wasVerified     = true;

               }
               // Otherwise, it is a POP3 or IMAP server
               else {

                    store = mailSession.getStore( protocol );
                    store.connect(arguments.host, arguments.port, arguments.user, arguments.password);
                    store.close();
                    // if we reached here, the credentials should be verified
                    status.wasVerified     = true;

               }         

         }
         //for authentication failures
         catch(javax.mail.AuthenticationFailedException e) {
                   status.errorType     = "Authentication";
                 status.errorDetail     = e;
            }
         // some other failure occurred like a javax.mail.MessagingException
         catch(Any e) {
                 status.errorType     = "Other";
                 status.errorDetail     = e;
         }


         // always close the stream ( messy work-around for lack of finally clause prior to CF9...)
         if ( not IsSimpleValue(ps) ) {
               ps.close();
         }

         return status;
    </cfscript>
</cffunction>

...Read More

Railo: CFPOP + Gmail + SSL Experiment

An interesting question on stackoverflow.com this week prompted me to explore cfmail/cfpop settings in the three engines: Railo, OpenBD and Adobe CF. If you have used gmail from ColdFusion, no doubt you are aware of one of the work-arounds for the lack of SSL support for CFPOP. Being new to Railo, I did not realize the work-around is only for Adobe CF.


Apparently Adobe CF also checks the default java.lang.System properties when creating mail connections. If certain mail settings like mail.pop3.socketFactory.class are present, it applies them to the new connection. However, Railo does not. It only uses the supplied tag attributes, which do not include "useSSL". At least as far as I know. So the work-around has no effect in Railo.

After poking around the API, I did manage to get CFPOP working with gmail under Railo.. with a catch. You can specify which provider to use for the pop3 protocol as the default provider for the pop3 protocol by adding a javamail.providers file to your {java_home}\lib directory.

Display {Java_Home} Path:

<cfset javaHome = createObject("java", "java.lang.System").getProperty("java.home", "")>
<cfoutput>
    javaHome\lib = #javaHome#\lib
</cfoutput>

Simply add the following line, and save the file as javamail.providers. (The ".providers" file extension is important). All CFPOP connections will now use SSL.

javamail.providers
protocol=pop3; type=store; class=com.sun.mail.pop3.POP3SSLStore; vendor=Sun Microsystems, Inc;

Now, I mentioned a catch. Unlike the work-around for Adobe CF, this setting is all-or-nothing. If you enable it, all CFPOP connections will use SSL. If you connect to a server does not support SSL, the connection will fail. With the Adobe CF work-around both connection types are allowed by setting the property mail.pop3.socketFactory.fallback  equal to true. So SSL will be used if supported. Otherwise, CF will fall back to a regular socket connection.  Keep in mind both work-arounds are system wide. So the settings apply to the entire JVM.

Now, the fix may be too broad for some. But if you only need SSL connections for CFPOP, it might do the trick. If not, there are other options like sTunnel and custom CFC's. I am sure Railo will implement official support for SSL with CFPOP one of these days. Hopefully, Adobe CF will too ;)

...Read More

Wednesday, April 21, 2010

ColdFusion: ZXing - Read / Write QRCode Barcode Example

I have been slowly working my way through more of the zxing library. Since I did not find any CF examples in my searches, I am posting some of my code snippets in case it helps someone else.


Generate Barcode
To generate a barcode, I first created an instance of the QRCodeWriter. Then called its encode() method with four settings: the text to encode, barcode type and the desired width and height. The encode() method does it magic and returns a matrix of bytes. Next I used the MatrixToImageWriter class, and its toBufferedImage(), method to convert the matrix into something useful. Not surprisingly, it returns a BufferedImage which is easily converted to a CF image object.

Note: This example uses the JavaLoader.cfc

<!---
    Generate barcode
--->
<cftry>
    <cfset origText = "http://code.google.com/p/zxing/wiki/GettingStarted" />
    <!--- initialize writer and create a new barcode matrix --->
    <cfset BarcodeFormat = loader.create("com.google.zxing.BarcodeFormat") />
    <cfset writer = loader.create("com.google.zxing.qrcode.QRCodeWriter").init() />
    <cfset bitMatrix = writer.encode( origText, BarcodeFormat.QR_CODE, 80, 80 )>
    <!--- render the matrix as a bufferedimage --->
    <cfset converter = loader.create("com.google.zxing.client.j2se.MatrixToImageWriter")>
    <cfset buff = converter.toBufferedImage( bitMatrix ) />
    <!--- convert it to a CF compatible image --->
    <cfset img = ImageNew( buff ) />

    <!--- display results --->
    <b>Original Text = </b> <cfoutput>#origText#</cfoutput>
    <div>
        <cfimage action="writeToBrowser" source="#img#" format="png">
    </div>
    <!--- add real exception handling here ...--->
    <cfcatch>
        ERROR: Unable to generate barcode <cfoutput>#cfcatch.message#</cfoutput>
    </cfcatch>
</cftry>

Read Barcode
Now to read / decode, I passed the BufferedImage into a series of classes that attempt to locate a barcode within an image and essentially extract it into matrix of bits. Finally, I passed the matrix into a QRCodeReader for decoding and voila - it returned the decoded text.

Result:


<!---
    Decode barcode
--->
<cftry>
    <!--- extract the BufferedImage of the current barcode --->
    <cfset buff = ImageGetBufferedImage( img ) />
    <!--- prepare the image for decoding --->
    <cfset source = loader.create("com.google.zxing.client.j2se.BufferedImageLuminanceSource").init( buff ) />
    <cfset binarizer = loader.create("com.google.zxing.common.GlobalHistogramBinarizer").init( source ) />
    <cfset bitmap = loader.create("com.google.zxing.BinaryBitmap").init( binarizer ) />
    <cfset reader = loader.create("com.google.zxing.qrcode.QRCodeReader").init() />
    <!--- decode the barcode. skipping "hints" just for simplicity --->
    <cfset decodedResult = reader.decode( bitmap, javacast("null", "")) />
    
    <!--- display results --->
    <b>Decoded Text = </b> <cfoutput>#decodedResult.getText()#</cfoutput>

    <!--- add real exception handling here ...--->
    <cfcatch>
        ERROR: Unable to generate barcode <cfoutput>#cfcatch.message#</cfoutput>
    </cfcatch>
</cftry>

Related Entries

...Read More

Monday, April 19, 2010

ColdFusion: iText / Add JavaScript To Form Example

I occasionally see questions about adding javascript to existing pdf's. There are (of course) some great examples on the iText site. I thought this CF translation of the AddJavaScriptToForm example might be helpful. If you review the code and comments, it actually simpler than it looks. There are some minor differences to compensate for the older iText jars in CF9 and CF8. As well as some name changes to avoid reserved word conflicts.

Source: http://1t3xt.info/examples/browse/?page=example&id=438


Javascript Code (CF8 + CF9)

<cfsavecontent variable="jsCode">
    function setReadOnly(readonly) {
        var partner = this.getField('partner');
        if(readonly) {
            partner.value = '';
        }
        partner.readonly = readonly;
    }
    function validate() {
        var married = this.getField('married');
        var partner = this.getField('partner');
        if (married.value == 'yes' && partner.value == '') {
            app.alert('please enter the name of your partner');
        }
        else {
            var prop = new Object();
            prop.cURL = "http://1t3xt.info/examples/request.php";
            prop.cSubmitAs = "HTML";
            this.submitForm( {  
                                cURL: "http://1t3xt.info/examples/request.php",
                                cSubmitAs: "HTML"
                              } 
                            );            
        }
    }
</cfsavecontent>

1) Create a Form (CF9 + CF8)
Note: CF8 does not support "finally". So for compatibility, just change the try/finally clause to a try/catch.
<cfscript>
    outputPath     = ExpandPath("form_without_js.pdf");
    document = createObject("java", "com.lowagie.text.Document").init();

    try {
        stream = createObject("java", "java.io.FileOutputStream").init( outputPath );
        writer = createObject("java", "com.lowagie.text.pdf.PdfWriter").getInstance(document, stream);
        document.open();
         
         Element = createObject("java", "com.lowagie.text.Element");
        BaseFont = createObject("java", "com.lowagie.text.pdf.BaseFont");
        //Note: CF8 + CF9 iText versions do not have a createFont() method with zero params
        bf = BaseFont.createFont( BaseFont.HELVETICA, BaseFont.WINANSI, false );
        directcontent = writer.getDirectContent();
        directcontent.beginText();
        directcontent.setFontAndSize(bf, 12);
        directcontent.showTextAligned( Element.ALIGN_LEFT, "Married?", 36, 770, 0 );
        directcontent.showTextAligned( Element.ALIGN_LEFT, "YES", 58, 750, 0);
        directcontent.showTextAligned( Element.ALIGN_LEFT, "NO", 102, 750, 0);
        directcontent.showTextAligned( Element.ALIGN_LEFT, "Name partner?", 36, 730, 0 );
        directcontent.endText();

        // initialize reusable objects
         Rectangle = createObject("java", "com.lowagie.text.Rectangle");
         PdfFormField = createObject("java", "com.lowagie.text.pdf.PdfFormField");
         RadioCheckField = createObject("java", "com.lowagie.text.pdf.RadioCheckField");
         TextField = createObject("java", "com.lowagie.text.pdf.TextField");
         Color = createObject("java", "java.awt.Color");

        married = PdfFormField.createRadioButton(writer, true);
        married.setFieldName("married");
        writer.addAnnotation( married );
        
        // Note: Field names changed to avoid CF reserved word conflicts (ie "yes", "no")
        married.setValueAsName("yes");
        rectYes = Rectangle.init( 40, 766, 56, 744 );
        yesField = RadioCheckField.init(writer, rectYes, javacast("null", ""), "yes");
        yesField.setChecked(true);
        married.addKid( yesField.getRadioField() );
        rectNo = Rectangle.init( 84, 766, 100, 744 );
        noField = RadioCheckField.init(writer, rectNo, javacast("null", ""), "no");
        noField.setChecked(false);
        married.addKid( noField.getRadioField() );
        writer.addAnnotation( married );
 
         rect = Rectangle.init( 40, 710, 200, 726 );
        partner = TextField.init( writer, rect, "partner" );
        partner.setBorderColor( Color.BLACK );
        partner.setBorderWidth( 0.5 );
        writer.addAnnotation( partner.getTextField() );
    
        document.close();
        WriteOutput("File created! "& outputPath &"<hr>");
    }
       // cleanup  
    finally {
        if (isDefined("document")) {
            document.close();
        }        
        if (isDefined("stream")) {
            stream.close();
        }        
    }
</cfscript>

2) Add the Javascript (CF9 Only)
<cfscript>
    inputPath     = ExpandPath("form_without_js.pdf");
    outputPath     = ExpandPath("form_plus_js.pdf");
    document = createObject("java", "com.lowagie.text.Document").init();
    
    try {
        // read in the pdf form
        reader = createObject("java", "com.lowagie.text.pdf.PdfReader").init( inputPath );
        stream = createObject("java", "java.io.FileOutputStream").init( outputPath );
        stamper = createObject("java", "com.lowagie.text.pdf.PdfStamper").init( reader, stream );

        // add javascript functions to the document
        stamper.getWriter().addJavaScript( jsCode );

        // create reference objects
        PdfName = createObject("java", "com.lowagie.text.pdf.PdfName");
        PdfAction = createObject("java", "com.lowagie.text.pdf.PdfAction");
        PdfDictionary = createObject("java", "com.lowagie.text.pdf.PdfDictionary");
        PushbuttonField = createObject("java", "com.lowagie.text.pdf.PushbuttonField");
        
        // extract the parent option from the form 
        formObj = stamper.getAcroFields();
        fd = formObj.getFieldItem("married");

        // retrieve the dictionaries for "yes" radio button
        // note: CF9 iText version does not have the getWidgetRef(index) method
         dictYes = reader.getPdfObject( fd.widget_refs.get(0) );
        yesAction = dictYes.getAsDict( PdfName.AA );
        if (not IsDefined("yesAction")) {
            yesAction = PdfDictionary.init();
        }
        // add an onFocus event to this field
        yesAction.put( PdfName.init("Fo"), PdfAction.javaScript("setReadOnly(false);", stamper.getWriter()));
        dictYes.put( PdfName.AA, yesAction );

        // retrieve the dictionaries for "no" radio button
        dictNo = reader.getPdfObject( fd.widget_refs.get(1));
        noAction = dictNo.getAsDict( PdfName.AA );
        if (not IsDefined("noAction")) {
            noAction = PdfDictionary.init();
        }    
        // add an onFocus event to this field
        noAction.put( PdfName.init("Fo"), PdfAction.javaScript("setReadOnly(true);", stamper.getWriter()));
        dictNo.put(PdfName.AA, noAction);
 
         // create a submit button
        writer = stamper.getWriter();
        button = PushbuttonField.init(    writer, Rectangle.init(40, 690, 200, 710), "submit" );
        button.setText( "validate and submit" );
        button.setOptions( PushbuttonField.VISIBLE_BUT_DOES_NOT_PRINT );
        validateAndSubmit = button.getField();
        // this will call the validate function when the button is clicked
        validateAndSubmit.setAction( PdfAction.javaScript("validate();", stamper.getWriter()) );
        // add the button to page 1
        stamper.addAnnotation(validateAndSubmit, 1);

        WriteOutput("File created! "& outputPath &"<hr>");
    }
    finally {
        // cleanup
        if (isDefined("stamper")) {
            stamper.close();
        }        
        if (isDefined("stream")) {
            stream.close();
        }        
    }
</cfscript>

2) Add the Javascript (CF8 Only)
<cfscript>
    inputPath     = ExpandPath("form_without_js.pdf");
    outputPath     = ExpandPath("form_plus_js.pdf");
    document = createObject("java", "com.lowagie.text.Document").init();
    
    try {
        // read in the pdf form
        reader = createObject("java", "com.lowagie.text.pdf.PdfReader").init( inputPath );
        stream = createObject("java", "java.io.FileOutputStream").init( outputPath );
        stamper = createObject("java", "com.lowagie.text.pdf.PdfStamper").init( reader, stream );

        // add javascript functions to the document
        stamper.getWriter().addJavaScript( jsCode );

        // create reference objects
        PdfName = createObject("java", "com.lowagie.text.pdf.PdfName");
        PdfAction = createObject("java", "com.lowagie.text.pdf.PdfAction");
        PdfDictionary = createObject("java", "com.lowagie.text.pdf.PdfDictionary");
        PushbuttonField = createObject("java", "com.lowagie.text.pdf.PushbuttonField");
        
        // prepare to extract form field objects
        formObj = stamper.getAcroFields();
        
        // get parent option
        fd = formObj.getFieldItem("married");

        // retrieve the dictionaries for "yes" radio button
        // note: CF8 iText version does not have the getWidgetRef(index) method or getDirectObject()
         dictYes = reader.getPdfObject( fd.widget_refs.get(0) );
        yesAction = reader.getPdfObject(dictYes.get(PdfName.AA));
        if (not IsDefined("yesAction") or not yesAction.isDictionary()) {
            yesAction = PdfDictionary.init();
        }
        // add an onFocus event to this field
        yesAction.put( PdfName.init("Fo"), PdfAction.javaScript("setReadOnly(false);", stamper.getWriter()));
        dictYes.put( PdfName.AA, yesAction );

        // retrieve the dictionaries for "no" radio button
        dictNo = reader.getPdfObject( fd.widget_refs.get(1));
        noAction = reader.getPdfObject( dictNo.get(PdfName.AA) );
        if (not IsDefined("noAction") or not noAction.isDictionary()) {
            noAction = PdfDictionary.init();
        }    
        // add an onFocus event to this field
        noAction.put( PdfName.init("Fo"), PdfAction.javaScript("setReadOnly(true);", stamper.getWriter()));
        dictNo.put(PdfName.AA, noAction);
 
         // create a submit button
        writer = stamper.getWriter();
        button = PushbuttonField.init(    writer,
                                        Rectangle.init(40, 690, 200, 710), 
                                        "submit"
                                    );
        button.setText( "validate and submit" );
        button.setOptions( PushbuttonField.VISIBLE_BUT_DOES_NOT_PRINT );
        validateAndSubmit = button.getField();
        // this will cal the validate function when the button is clicked
        validateAndSubmit.setAction( PdfAction.javaScript("validate();", stamper.getWriter()) );
        // add the button to page 1
        stamper.addAnnotation(validateAndSubmit, 1);

        WriteOutput("File created! "& outputPath &"<hr>");
    }
    catch(Any e) {
        WriteOutput("ERROR: "& e.message);
    }
    if (isDefined("stamper")) {
        stamper.close();
    }        
    if (isDefined("stream")) {
        stream.close();
    }        
</cfscript>

...Read More

Saturday, April 17, 2010

CFChart Tip (Multiple background colors)

A recent question on CF-Talk asked if it were possible to create multiple background colors with cfchart. I was excited to discover it is possible using the Y-Axis "limits" attribute.  I should have known I was not the first one to figure this out.  Well, in case you too missed the original entry, here it is:


 Example of multiple background colors using Y-Axis "limits"

...Read More

Wednesday, April 14, 2010

How to Install JODConverter 3.0 under ColdFusion 8

ColdFusion 8 Instructions

1. Download latest version of the JODConverter and unzip.

2. Copy all jars from the jodconverter-core-3.0-beta-3\lib directory into the CF classpath. (Your version numbers may vary)

Note: I am not sure if all of the jars are needed. But this is the configuration that I used.


Example: Into WEB-INF\lib

C:\ColdFusion8\wwwroot\WEB-INF\lib\commons-cli-1.1.jar
C:\ColdFusion8\wwwroot\WEB-INF\lib\commons-io-1.4.jar
C:\ColdFusion8\wwwroot\WEB-INF\lib\jodconverter-core-3.0-beta-3.jar
C:\ColdFusion8\wwwroot\WEB-INF\lib\json-20080701.jar
C:\ColdFusion8\wwwroot\WEB-INF\lib\juh-3.1.0.jar
C:\ColdFusion8\wwwroot\WEB-INF\lib\jurt-3.1.0.jar
C:\ColdFusion8\wwwroot\WEB-INF\lib\ridl-3.1.0.jar
C:\ColdFusion8\wwwroot\WEB-INF\lib\unoil-3.1.0.jar

3. Smart people will make a back-up copy of the jvm.config file here.

4. Open the jvm.config file and locate the java.library.path value. Append the appropriate OpenOffice directory, using a comma. The exact directory will depend on your O/S. See the configuration page for details.

Example: On windows the new value might be:
java.library.path={application.home}/../lib,{application.home}/../jintegra/bin,{application.home}/../jintegra/bin/international,C:/Program Files/OpenOffice.org 3/URE/bin 

4. Stop and restart the CF Server

...Read More

JODConverter 3.0 + OpenOffice + CF8

I stumbled across a new version of the JODConverter a few weeks ago, and finally got around to testing it. Version 3.0 has some interesting new features over the previous version. For one thing, it no longer requires running OpenOffice as a service. The JODConverter can start an instance of OpenOffice on demand, like CF9. Both socket and named pipe connections are supported on windows. There are also new features like pooling and automatic restart, geared towards "improving the reliability and scalability of working with an external OOo [OpenOffice] process.". So I thought it was definitely worth a look (for Word to HTML conversions)


Basic Conversion Example
I decided to test it under CF8 first. Since I was not running OpenOffice as a service, the first thing I needed to do was fire up a new instance of OpenOffice. Now to start or stop OpenOffice, you will need an OfficeManager object. The simplest way to create one is using the DefaultOfficeManagerConfiguration class. Just create a new configuration object, then call the buildOfficeManager() method. This will create and return a new OfficeManager object pre-configured with the default settings, which should work in most environments. Then call OfficeManager.start() to kick-off the OpenOffice process.


<cfscript>
    Config  = createObject("java", "org.artofsolving.jodconverter.office.DefaultOfficeManagerConfiguration").init();
    Config.setOfficeHome("C:\Program Files\OpenOffice.org 3\");
    Manager = Config.buildOfficeManager();
    Manager.start();
</cfscript>

To convert a document, you first need to create an OfficeDocumentConverter object by passing in your OfficeManager. Then simply call the convert() method with your two files (ie input and output). Finally, use the OfficeManager to stop the OpenOffice instance. That is it.

<cfscript>
    inPath = "c:\docs\myTestDocument.docx";
    outPath = "c:\docs\myTestDocument_Converted.html";

    OfficeDocumentConverter = createObject("java", "org.artofsolving.jodconverter.OfficeDocumentConverter");
    converter = OfficeDocumentConverter.init( Manager );
    input = createObject("java", "java.io.File").init( inPath );
    output = createObject("java", "java.io.File").init( outPath );
    Converter.convert(input, output);
    WriteOutput("Output file created: "& output);

    Manager.stop();
</cfscript> 

Obviously it would be silly to start and stop OpenOffice every time you needed to do a conversion. So a better alternative might be to initialize the OfficeManager once in your Application.cfc, and reuse it. Perform the initialization code in onApplicationStart and add your OfficeManager to the application scope. Then do the cleanup code (ie stopping OpenOffice) in onApplicationEnd. Other than grabbing the OfficeManager from the application scope first, the conversion code is exactly the same.

Application.cfc (Not including error handling, logging, etcetera)
<cfcomponent>
 <cfset this.name = "jodconverterSample" />
 <cfset this.applicationTimeOut = createTimeSpan(0, 1, 0, 0) />

 <cffunction name="onApplicationStart" returnType="void">
  <cfset var Manager = "" />
  <cfset var Config  = "" />

  <!--- start an instance with the default settings --->
  <cfset Config  = createObject("java", "org.artofsolving.jodconverter.office.DefaultOfficeManagerConfiguration").init() />
  <cfset Config.setOfficeHome( "C:\Program Files\OpenOffice.org 3\" ) />
   <cfset Manager = Config.buildOfficeManager() />
   <cfset Manager.start() />

  <cfset application.OfficeManager = Manager />

 </cffunction>

 <cffunction name="onApplicationEnd" returnType="void">
     <cfargument name="appScope" type="any" required="true" />
  <!--- stop the instance --->
  <cfset appScope.OfficeManager.stop() />
 </cffunction>
 
</cfcomponent>

Settings
Earlier, I mentioned there are different types of connections and managers. The DefaultOfficeManagerConfiguration creates a socket connection on port 2002 by default. But you can use the available methods to change the port number, connection type, and a bunch of other settings. For example, you could create a named pipe connection instead. Just set the appropriate configuration properties before creating the OfficeManager.

<cfscript>
    Config  = createObject("java", "org.artofsolving.jodconverter.office.DefaultOfficeManagerConfiguration").init() />
    Config.setOfficeHome( "C:\Program Files\OpenOffice.org 3\" ) />
    // use named pipe connection 
    Protocol = createObject("java", "org.artofsolving.jodconverter.office.OfficeConnectionProtocol");
    Config.setConnectionProtocol( Protocol.PIPE );
    Config.setPipeName( "myApp_jod_pipe"  );
    // kill any task that takes longer than 2 minutes 
    Config.setTaskExecutionTimeout((2 * 60 * 1000));
    Manager = Config.buildOfficeManager();
    // ....
</cfscript>

You could also connect to an external instance that is already running using the ExternalOfficeManagerConfiguration class instead. Though when working with an external process (ie controlled elsewhere ), obviously you do not start() or stop() it. Just connect to it.

Web Application / Servlet
Though JODConverter can be used strictly as a java library, there is also a sample web application/servlet available. It is not currently part of the distribution jar, but you can find it under the project source tab. Just be aware the web application does not fully handle conversions to HTML out-of-the-box. The servlet will produce a single html file, minus any images. As mentioned in the FAQ's, that is by design. The best way to handle images really "..depends on your particular requirements". So the implementation of image handling is deliberately left up to you. For more details see the FAQ's.

OpenOffice Quirks
Obviously, OpenOffice has some quirks of its own. Though it does a pretty good job with most documents, it is not perfect. It almost certainly will not be able to convert everything you throw at it. So any conversion code should definitely incorporate some solid error handling.

CF9 Quirks
I was very curious to see how well all of the pieces worked together under CF9. As I expected, there were a few quirks.

For whatever reason, things only worked smoothly when the JODConverter's instance of OpenOffice was started after CF9 started its instance. In other words, I had to run a small Word to Pdf conversion first, to force CF to start its OpenOffice process. Then afterward start the JODConverter's instance. When they were not started in that order, all sorts of errors ensued. Both from CF and the JODConverter.

While I had success with socket and external connections, I had zero luck getting a separate named pipe connection to work alongside CF9's instance. Initially I thought it should be possible. But I am not very well versed in UNO or named pipes. So that could just be ignorance on my part. If anyone does know the answer, one way or the other, let me know.

Conclusions
All in all, I was pleased with my initial tests. Though I am still not completely comfortable with the automatic restart feature. I can definitely see its value. OpenOffice can, and on occasion, does crash. But anything that automatically revives itself after death, tends to make me think of zombies ;) So I think I will need to study it (and how to best manage OOO instances) further.

...Read More

Thursday, April 1, 2010

OT: April 1 (Take Your Marks ... Get Set ... Go)

I see the day is already off to a rousing start.
http://googleblog.blogspot.com/2010/04/different-kind-of-company-name.html

Wake me when it is over ...

...Read More

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep