ColdFusion and LiveCycle ES: Reader Extensions

Thursday, May 21, 2009

With this post, I aim to

  1. walk through the steps I took to get LiveCycle configured for applying Reader Extensions
  2. show how to invoke the ReaderExtensions webservice with ColdFusion

With this post, I do not aim to show you how you’d actually use the ReaderExtensions in real life. I have no idea if “real” production applications would be invoking these services; It seems that “The LC Way” involves you creating new processes in LC Workbench and using watched folders and such. I do not know what benefits are conferred by that approach over the approach I’ll demonstrate below. Again, as I said in my first post on this topic, this series is my way of recording what I went through to get this stuff working as I build a proof-of-concept.

Configuring LiveCycle

Login to LC, and click “Services”. Then go to “Applications and Services”, then “Services Management”. In the resulting dropdown, select “Reader Extensions” and submit. Check off the “ReaderExtensionsService” checkbox, and hit the “start” button if it’s not started.

Click on that link, and then select the checkbox beside “Default Soap Endpoint”. Click the “enable” button if it’s not enabled.

Click on the Services tab, and from there you can control whether to require authentication (which you’ll pass in the “creds” struct below) and also how to run the service. I kept mine at “System”, but I imagine in real production you’d run it as something else. I’m no LC expert, so I have no valid opinions here.

Invoking the ReaderExtensions webservice with ColdFusion

Invoking the ReaderExtensionsService from ColdFusion is reasonably easy, once you get around a few gotchas. First, you will probably need to pass authentication credentials. To get started, I just used the credentials I use to log in to LC Admin. You can turn off the need for credentials by going into LC Admin, navigating to the ReaderExtensionsService config, and turning authentication off.

According to the docs, you can get your extended PDF back from the webservice in 4 formats: base64-encoded, mime, dime, and as binary via a URL. I’ll show you the base-64 and remote URL approaches.

Base64

<cfscript>
creds = {username='administrator',password='password'};
serviceRoot = "http://localhost:8080/soap/services/ReaderExtensionsService";
wsdl = "#serviceRoot#?wsdl";
ws = createObject("webservice",wsdl,creds);
ws._setProperty("javax.xml.rpc.service.endpoint.address","#serviceRoot#?blob=base64");

filePath = expandPath("readme.pdf");
theFile = fileReadBinary(filePath);

inPDFDoc = {contentType="application/pdf",binaryData=theFile};

usageRights = {enabledComments=true,enabledCommentsOnline=true,
 enabledDigitalSignatures=true,enabledDynamicFormPages=true,enabledFormFillIn=true,
 enabledOnlineForms=true};
applyOptions = {message="",modeFinal=true,usageRights=usageRights};

response = ws.applyUsageRights(inPDFDoc,"marc","njUvw9Lnftxxxx",applyOptions);

outputData = toBinary(response.getBinaryData());
</cfscript>
<cfdump var="#outputdata#">

<cfset newfile = expandPath("readme_extended_withbinary.pdf")>
<cffile action="write" file="#newFile#" output="#outputData#">

Notables

First, note the very important couple of lines for getting LC to return the data in base64 format. Possibly this trick is new to you. It was to me, too, before I needed to dig into CF webservices and find out how to change the endpoint address after setting the wsdl in the createObject call. If you wanted mime or dime return, you’d do blob=dime or blob=mime, respectively. You can also do blob=http and it’ll give you the same result as the way I’ll do it down below. This endpoint resetting really tripped me up at first. I kept trying to call response.getBinaryData(), and the binary was empty. So, this is for you google searchers: empty binary data.

Second, note that in your call to applyUsageRights, you need to pass valid user credentials. That crazy username/password combo was what I got from LC when I downloaded the trial version.

Finally, the webservice call returns a datatype named “Blob”. No, this isn’t like a database blob. Instead, it’s an object of Adobe’s devising (I presume) that contains several methods you can call. CFDUMP it to see what’s available to you. Anyway, when you specify blob=base64 in the endpoint address, you can get back the new reader-extended PDF by calling response.getBinaryData(), and then you write that to a new file. Not that it matters, but I really like the Blob return type. It’s a great way to get consistent webservice calls across the various LC services. Props, dudes.

RemoteURL

<cfscript>
creds = {username='administrator',password='password'};
ws = createObject("webservice","http://localhost:8080/soap/services/ReaderExtensionsService?wsdl",creds);

filePath = expandPath("readme.pdf");
theFile = fileReadBinary(filePath);

inPDFDoc = {contentType="application/pdf",binaryData=theFile};

usageRights = {enabledComments=true,enabledCommentsOnline=true,
 enabledDigitalSignatures=true,enabledDynamicFormPages=true,enabledFormFillIn=true,
 enabledOnlineForms=true};
applyOptions = {message="",modeFinal=true,usageRights=usageRights};

response = ws.applyUsageRights(inPDFDoc,"marc","njUvw9Lnftxxxx",applyOptions);
</cfscript>

<cfset newfile = expandPath("readme_extended_withRemoteURL.pdf")>

<cfhttp url="#response.getRemoteURL()#">
<cfdump var="#cfhttp#">

<cffile action="write" file="#newFile#" output="#cfhttp.filecontent.toByteArray()#">

Notables

Note that I’m using normal old createObject with a wsdl path… no resetting the endpoint here. When you call the service by default you get the response via a remote URL, NOT binary. This means you need to do a cfhttp call to the response object’s getRemoteURL() method, and then you use cfhttp.filecontent.toByteArray() to write it to a new file.

Finally, I’m using the inline struct notation in these examples, but if you’re on a previous version of CF you can convert this code to normal old structnew() and you’ll be fine. Nothing about these calls should be specific to a version of CF.

Credits

Props to this blog post, which was the first post I read on using ColdFusion and LC together. This saved me a lot of time getting started.

No comments: