Archive for the ‘ColdFusion’ Category

Streaming a pdf from a streamReader to a browser with ColdFusion 10

August 26, 2014

Following up on my article on ‘retrieving a MIME-encoded binary attachment through MTOM with ColdFusion¬†10’, it was only logical that I had to send the content i retrieved to the browser.
Remember, we received the content from a webservice through a streamReader.

Now there’s two ways to go at it sending it to the browser. One could appended all the bytes from the stream to variable, and wrap it in a cfcontent tag to send it to the browser. This would of course consume a substantial amount of memory from the JVM running the CF engine, because the whole content has to be recreated in memory.

In my particular case, the content would be pdf files, which can be streamed to pdf reader plugins in the browser. The advantage of streaming a pdf file is that an intelligent pdf reader can already render the first pages while the next pages are still being received, hence for large pdf files the user doesn’t have to wait until the complete document is downloaded before he can start reading it.

Additionally, if we could forward the stream we receive from the webservice directly to the browser, the only memory we would consume would theoretically be the size of our byteArray buffer.

I found a few articles on streaming binary content to a browser, all of which dated before 2011. Unfortunately none of those worked on my ColdFusion 10 server, maybe because of the different java application server running under the hood, prior to version 10. It took a little digging to find out a working equivalent in CF10.

In the code below, I wrapped the call to the WebService that offers the pdf files in a DAO. The nice thing about CFC’s is that apparetly they can also return java objects of type streamReader if you declare the returntype to ‘Any’.

In my view template, I will call the GetStream method on the DAO, and I will stream the content I receive directly to the ColdFusion Output buffer and flush after each 1024 bytes (size could perhaps be chosen better).

The DAO method:


public Any function GetStream(string docType='', numeric docNumber='', numeric docYear='', string docQualifier='', DataType.LanguageIdentifier language) output = true {
var _variables = StructNew();
var _fds=CreateObject("Java", "javax.activation.FileDataSource").init('F:\ColdFusion10\cfusion\tmpCache\temp.pdf');
var _dhl=CreateObject("Java", "javax.activation.DataHandler").init(_fds);

_variables.ws=createObject(“webservice”,”http://contentServer/DocContentServiceWS/DocContentServiceWSPort?WSDL”,{wsversion=”2″,refreshwsdl=”false”,timeout=25});
_variables.result = StructNew();
_variables.result.filenameOut = ”;
_variables.result.errorMessage = ”;
_variables.result.errorCode = ”;
_variables.response=_variables.ws.getmanifestationcontent(‘USERNAME’,arguments.docType,JavaCast(“bigdecimal”,arguments.docNumber),JavaCast(“bigdecimal”,arguments.docYear),arguments.docQualifier,UCase(arguments.language.identifier),’INTRA’,’PDF’,JavaCast(“bigdecimal”,1),_variables.result.filenameOut,_dhl,_variables.result.errorMessage,_variables.result.errorCode);
_variables.inputStream=_variables.response.getFileContent_out().getInputStream();
/*
_variables.outStream=createObject(“Java”,”java.io.ByteArrayOutputStream”).init();
_variables.byteClass=createObject(“Java”,”java.lang.Byte”).TYPE;
_variables.byteArray=createObject(“Java”,”java.lang.reflect.Array”).newInstance(_variables.byteClass, javacast(“int”, 1024));
_variables.length=_variables.inputStream.read(_variables.byteArray);
_variables.offset=0;
while (_variables.length GT 0) {
_variables.outStream.write(_variables.byteArray,_variables.offset,_variables.length);
_variables.length=_variables.inputStream.read(_variables.byteArray);
}
_variables.outStream.close();
_variables.inputStream.close();
*/
return _variables.inputStream;
}

The view template:

FileInputStream = documentsDAO.GetStream(URL.docType,URL.docNumber,URL.docYear,URL.docQualifier,language);
// Response = GetPageContext().GetResponse().GetResponse(); -- this doesn't work in CF10
Response = getPageContext().getFusionContext().getResponse();
Response.setHeader( 'Content-Type', 'application/pdf' );
Response.ResetBuffer();

outStream = Response.GetOutputStream();
byteClass=createObject(“Java”,”java.lang.Byte”).TYPE;
byteArray=createObject(“Java”,”java.lang.reflect.Array”).newInstance(byteClass, javacast(“int”, 1024));
length=FileInputStream.read(byteArray);
offset=0;
while (length GT 0) {
outStream.write(byteArray, offset, length);
outStream.Flush();
length = FileInputStream.read(byteArray);
}
Response.Reset();
FileInputStream.Close();
outStream.Close();

Advertisements

Web Services Atomic Transactions in ColdFusion

June 20, 2014

In order to be able to perform web services atomic transactions, we need to run the ColdFusion code
on a Java Application Server that supports WS-AT. Obviously the Tomcat that ships with ColdFusion does not
support it (out of the box). One could try to plugin the Atomikos transaction manager, but for the purpose of
this test, we decided to deploy ColdFusion on WebLogic and benefit from the WS-AT support that is offered by WebLogic.

In the example below, we will wrap 2 web service invocations in a transaction managed by WebLogic’s transaction manager.
The methods persistDetachedContact in WebServiceA and removeDetachedContactDetail in WebServiceB both require WSAT10,
which means that they can not be invoked if there is no transaction context.

// load webservice classes
wsclassA = CreateObject(“java”,”eu.alrighty.marbie.WebServiceA”);
wsclassB = CreateObject(java”,”eu.alrighty.marbie.WebServiceB”);

// load web service wsdl’s
wsdlUrlA = wsclassA.getClass().getClassLoader().getResource(“META-INF/wsdls/WebServiceAService.wsdl”);
wsdlUrlB = wsclassB.getClass().getClassLoader().getResource(“META-INF/wsdls/WebServiceBService.wsdl”);

// prepare transactional feature
feature = CreateObject(“java”,”weblogic.wsee.wstx.wsat.TransactionalFeature”);
ta = CreateObject(“java”,”weblogic.wsee.wstx.wsat.Transactional”);
flowtype = CreateObject(“java”,”weblogic.wsee.wstx.wsat.Transactional$TransactionFlowType”).SUPPORTS;
version = CreateObject(“java”,”weblogic.wsee.wstx.wsat.Transactional$Version”).WSAT10;
feature.setFlowType(flowtype);
feature.setVersion(version);

// get webLogic Transaction Manager
ctx = CreateObject(“java”,”javax.naming.InitialContext”);
tx = ctx.lookup(“java:comp/UserTransaction”);

// instantiate webService stubs through wsdl’s
wsa = CreateObject(“java”,”eu.alrighty.marbie.WebServiceAService”).init(wsdlUrlA);
wsb = CreateObject(“java”,”eu.alrighty.marbie.WebServiceBService”).init(wsdlUrlB);

// start the transaction
tx.getTM().begin();

// find a contact person from webService A, and create a new contact person with it (increase Id);
contacts = wsa.getWebServiceAPort([feature]).findAllContacts();
contact = contacts[ArrayLen(contacts)];
contact.setId(IncrementValue(contact.getId()));
writeDump(contact.getId())

// find a contact detail from webService B, and create a new contactdetail with it (increase Id);
contactdetails = wsb.getWebServiceBPort([feature]).findAllContactdetails();
contactdetail = contactdetails[ArrayLen(contactdetails)];
contactdetail.setId(IncrementValue(contactdetail.getId()));
writeDump(contactdetail.getId());

// add contact to webService A
writeDump(wsa.getWebServiceAPort([feature]).persistDetachedContact(contact));
// add the contact detail to webService B
writeDump(wsb.getWebServiceBPort([feature]).removeDetachedContactDetail(contactdetail));

// (optionally) dump some information on the status of the transaction
writeDump(tx.getTM().getTransaction().getStatus());

// commit the transaction
tx.getTM().commit();

In the code above, a contact person is added to a database through web service A, running on a
WebLogic A. Secondly, a (non existing) contact detail is removed from a database through web service B, running on a
WebLogic B. Since the second command can not be executed, the first one is properly rolled back, and the test
ends leaving the data in the database to its’ original state.

(Thanks to Petar Banicevic for helping me out with the weblogic transaction manager).

Retrieving a MIME-encoded binary attachment through MTOM with ColdFusion 10

March 6, 2013

Setup:
A Oracle Weblogic (BEA) Web Service returning mime-encoded binary documents (word or pdf) in attachment through MTOM, matching the metadata specified in the request.
A client running on ColdFusion 10, using the automatically generated stub from the WSDL of the Web Service using axis2.

To start, we have ColdFusion generate the stub by invoking the WSDL:

variables.ws=createObject("webservice","http://testserver/CarsDocContentServiceWS/CarsDocContentServiceWSPort?WSDL",{wsversion="2",refreshwsdl="false",timeout=500});

Iomportant: we need to specify wsversion = 2 to force ColdFusion to use axis2. Refreshwdl can be false, we only want the stub to be generated once. Regenerating it each time is time-consuming.
To see what we get back from the Web Service it is always a good idea to dump it:

writeDump(variables.ws);

dump of the webservice and its methods

The method we are looking for is getmanifestationcontent. The first 8 variables are the metadata we need to provide, the last 5 are returned by the Web Service. I create a placeholder for them.


variables.result=StructNew();
variables.result.filenameOut='';
variables.result.errorMessage='';
variables.result.errorCode='';

All set up to invoke the method on the webservice:


variables.response=variables.ws.getmanifestationcontent('MYACC','ST',JavaCast("bigdecimal",5007),JavaCast("bigdecimal",2012),'INIT','EN','INTRA','PDF',JavaCast("bigdecimal",1),variables.result.filenameOut,dhl,variables.result.errorMessage,variables.result.errorCode);

Some of the metadata we passed in had to be casted to the expected datatype. The one oddity in this particular webservice was the ‘dhl’ variable that matches the expected javax.activation.DataHandler data type. Since this is a returned argument, I expected it would be enough to pass just the name of an empty variable and the stub would automatically return a DataHandler object. But the webservice refused to accept any null valued arguments, so I had to build a empty DataHandler object in which even the DataSource had to be specified, even if it would never be used on the Web Service. So I added these 2 lines of code prior to the invocation of the Web Service.


fds=CreateObject("Java", "javax.activation.FileDataSource").init('c:\temp.pdf');
dhl=CreateObject("Java", "javax.activation.DataHandler").init(fds);

Time to look at the response of our request. I would have expected the response to be in the DataHandler variable dhl. But this was not the case. The attachent was in the response of the invocation of the getmanifestation method itself.
So, again, I dumped it to see what I was dealing with.

writeDump(variables.response);

manifestationcontent response

Clearly, the filename was in the get_FileName_out() method:

saveToFileName="c:\"&variables.response.getFileName_out();

Now the only thing left is to get the binary attachment and I saved it locally to the disk. The getFileContent_out() contained the getInputStream() i was looking for. So I could loop through that until the end-of-file and stream it to the local disk:


inputStream=variables.response.getFileContent_out().getInputStream();
outStream=createObject("Java","java.io.ByteArrayOutputStream").init();

byteClass=createObject("Java","java.lang.Byte").TYPE;
byteArray=createObject("Java","java.lang.reflect.Array").newInstance(byteClass, javacast("int", 1024));
length=inputStream.read(byteArray);
offset=0;
while (length GT 0) {
outStream.write(byteArray,offset,length);
length=inputStream.read(byteArray);
}
outStream.close();
inputStream.close();
FileWrite(saveToFileName,outStream.toByteArray());

While dumping the objects returned from the Web Sevice, I noticed that the Oracle WebLogic WebService used the Apache James Mime4J library. Since it was not by default in my list of libs that ColdFusion is using, I had to download it and put it in the cfusion/lib directory. I didn’t need to reference it anywhere in my code, but it would not run without it, specifically throwing an error telling me that ColdFusion could not find a specific class within this library.