Posted 03/02/2007 in coldfusion

We found the need to have dynamic PDFs generated, displayed to the user, then a copy stored version of the PDF in binary format in a MySQL database.

This is great for PDF-based invoices, confirmation letters, etc that need to be generated and stored. In our case - writing the PDF to a web-accessible directory made no sense because of the cluster. With a cluster it's taboo to be dynamically writing files to the shared filesystem because any number of inconsistencies can arise - especially in a high-load environment. This brings us to the need to store this PDF, as it was dynamically generated, in the database.

The first step is to create the field in which the binary PDF will reside - in our case creating a BLOB field in MySQL. Seeing as our PDFs are graphic-less, they will be small in size and a BLOB will more than suffice in terms of the size limitation.

The next is coming up with a process in Coldfusion that will pull a binary PDF created from the following CFDOCUMENT code:
<cfsetting enableCFoutputOnly="yes">
<cfdocument format="pdf" fontembed="yes">
	<div style="font-family:sans-serif; padding:10px;">
		<h2>This is a TEST PDF for ID## #FORM.ID#</h2>
		<h4>For demonstration purposes only.</h4>
<cfsetting enableCFoutputOnly="yes">
This will generate a PDF document and output it directly to the requester.

... hence the need for the next piece - the use of CFHTTP to send a request for this dynamic PDF and save the contents of the response - i.e. the binary PDF...
<cfsetting enableCFoutputOnly="yes">
<!--- fetch the dynamic PDF with dynamic variables --->
<cfhttp url="genPDF.cfm?ID=0123" resolveurl="Yes" throwOnError="Yes">
<!--- insert the PDF - cast to CF_SQL_BLOB and make sure cfhttp returns binary byteArray --->
<cfquery name="putPDF" datasource="PDFDSN">
	INSERT INTO pdfs (pdfContents) 
	values (<cfqueryparam cfsqltype="CF_SQL_BLOB" value="#cfhttp.filecontent.toByteArray()#">)
<!--- display the PDF - mind that cfcontent requires binary variable="" --->
<cfcontent type="application/pdf" variable="#cfhttp.filecontent.toByteArray()#">
<cfsetting enableCFoutputOnly="no">
And then save the binary PDF into the BLOB field we created for it as well as displaying it to the true user. This is a proxy of sorts - to make another HTTP request on top of the actual client's request.

And then when the user wants the fetch the already created binary PDF from the database on demand:
<cfsetting enableCFoutputOnly="yes">
<cfquery name="pullPDF" datasource="PDFDSN">
	SELECT pdfContents from pdfs LIMIT 1
<!--- create byteArray output stream --->
<cfset baos = createObject("java", "")>
<!--- write the byteArray stored in the DB to the output stream --->
<cfset baos.write(pullPDF.pdfContents)>
<!--- display the PDF to the user by geting the binary output stream --->
<cfcontent type="application/pdf" variable="#boas.toByteArray()#">
<cfsetting enableCFoutputOnly="no">

The biggest discovery of this task was finding the way to pull the binary data from the database and output it with the tag. This involved instantiating a new ByteArrayOutputStream object and using the ByteArrayOutputStream::write (have to love the Perl syntax) method to write the binary data pulled from the containing the binary PDF to the output stream. Then once the output stream has the binary byteArray - cfcontent requires the outputstream to have a variable that is binary - hence using the ByteArrayOutputStream::toByteArray method.

This works very well and is better than using a MySQL TEXT field and converting the binary data to base64() and back as so many people do. This will work with images and the like - basically anything that <cfcontent> will support.
new comment
EMAIL (hidden)
MESSAGE TAGS ALLOWED: <code> <a> <pre class="code [tab4|tabX|inline|bash]"> <br>