Mixing techniques causes a memory leak

A co-worker solved an interesting problem this week.

A number of developers have been contributing to a Groovy script that exports a large amount of data from one of our databases. Although the conversion rules for the data can be complex, at its core, the script is pretty simple. Using groovy.sql.Sql, the script does a SELECT to get a number of insurance policies. Then it iterates through the result set and does a second SELECT to get some additional information about each policy. Here’s the pseudocode:


sql = Sql.newInstance(connectionProperties)
String SELECT_POLICIES = "SELECT * FROM POLICIES WHERE STATUS = 'ACTIVE'"
sql.eachRow(SELECT_POLICIES) { polRow ->
String SELECT_DETAILS = "SELECT FIELDS FROM TABLE1, TABLE2, ETC. WHERE POLICY_NO = ${polRow.policy_no}"
sql.eachRow(SELECT_DETAILS) { detailRow ->
writeToFile(formatValues(detailRow))
}
}

Until the latest sprint, the script had been working fine. However, it recently began to cause OutOfMemory errors after writing about 60,000 rows, and none of the developers could figure out why. The main difference that we saw was that a new developer had modified one of the SQL statements, and instead of using string substitution to modify the inner query, he had used parameter markers — which is a technique that, really, we all should be adopting for a number of good reasons.


sql = Sql.newInstance(connectionProperties)
String SELECT_POLICIES = "SELECT * FROM POLICIES WHERE STATUS = 'ACTIVE'"
sql.eachRow(SELECT_POLICIES) { polRow ->
String SELECT_DETAILS = "SELECT FIELDS FROM TABLE1, TABLE2, ETC. WHERE POLICY_NO = ${polRow.policy_no} AND SOME_PROP = ?"
sql.eachRow(SELECT_DETAILS, [polRow.some_prop]) { detailRow ->
writeToFile(formatValues(detailRow))
}
}

My co-worker solved the puzzle for us. With the addition of the parameter in the statement, the Groovy Sql class started compiling the inner query into a prepared statement. Prepared statements are generally faster because they are compiled and then cached and re-used, so you don’t incur the overhead of having to recompile on each use. However, with the substitution of a different policy number in the string on each iteration of the loop, making each string unique, on each iteration of the loop the Sql processor was preparing and caching a new statement. When the cache filled with about 60,000 instances, we ran out of heap memory.

The solution, of course, was to get rid of all the string substitution in our SQL statements and use parameters instead. The benefit of forcing all the queries to use prepared statements was that the script now runs about 30% faster. For a process that used to take 4.5 hours, that’s a significant improvement.

Lesson #1: Use prepared statements. Not only are they more efficient, but if you’re working with a web application, they are resistant to SQL injection.
Lesson #2: Don’t mix techniques. Practice code review and pair programming to encourage all of your team to develop shared best practices.

Advertisements

#groovy, #groovy-sql, #memory-leak

Catching a dynamic typing failure in Groovy

Earlier today, a colleague asked me to help troubleshoot a very weird error in a Grails page he was working on.

On this page, he has a select element where the user is asked to choose a US state. The states list comes from a domain class that is mapped to a database table that holds state names and their ISO-8601 codes:

CODE_VALUE CODE_NAME
2 AK Alaska
9 DE Delaware
10 CA California
16 ID Idaho
50 TN Tennessee

<g:select name="state" from="${State.list()}" optionKey="codeValue" optionValue="codeName" value="${params.state}"/>

In the controller action for this page, he posts [params: params] back on the request in the return statement. So, after hitting submit on the page, he would expect to see that the select element retains the same state that was chosen by the user. (value=”${params.state}”)

The weird behavior he described was that the page seemed to work as designed for most values that he selected from the drop-down. However, for the first states in the list (AK through DE), upon submitting the form, the selected state would change. For example, if he selected “AL Alabama” (with a codeValue == 2), after submit, the form would have “TN Tennessee” selected. (codeValue == 50).

After puzzling through this for a while, we finally came to understand what was happening.

Groovy is dynamically typed. If you don’t explicitly specify a data type, then Groovy treats all values as a “def” – an instance of java.lang.Object. When Groovy has to perform an operation on a “def”, it makes an educated guess about what type to cast the value to, and it is right so often that we tend to forget that typing is happening at all.

But consider: Values posted back to the server from a GSP form in an http request are all returned as text. Grails conveniently parses form values out of the request and injects them into the controller action as a List named “params”, but it doesn’t know how to type them if they’re not actually Strings, so if you don’t cast them yourself, you get whatever type Groovy thinks they should be. My colleague was taking the params.state value right off of the request and then rendering it unmodified back into the view.

Now, if params.state was being typed as an Integer, everything should work as expected. But what if Groovy was blindly typing the value as a String? On a hunch, I asked my colleague to take a look at the
Unicode Basic Latin character set.

Case 1: He selects “ID Idaho”. Idaho’s codeValue, “16”, is submitted back to the controller and then reloaded into the select upon rendering. This works as expected.

Case 2: He selects “AL Alabama”. Alabama’s codeValue, “2”, is submitted back to the controller and then reloaded into the select upon rendering. The select now has “TN Tennessee”, codeValue “50”, selected.

The decimal Unicode value for the character “2” is 50.

What’s happening here is that if a two-character String representation of a numeric value (e.g., “16”) is given to Groovy, Groovy is correctly deducing that it needs to be cast as Integer. If a one-character String representation of a numeric value (e.g., “2”) is given to Groovy with no other instructions, Groovy is making the guess that this is a byte, and it is operating on the decimal value of the byte.

Once we understood what was happening, the fix was simple.

<g:select name="state" from="${State.list()}" optionKey="codeValue" optionValue="codeName" value="${params.state as Integer}"/>

I think that the lesson to be learned here is that while dynamically typed languages are great, a smart developer should never rely on them to always do the right thing. You don’t have to go so far as to force static typing in Groovy, but do use typed values as a habit, whenever you have the choice.

#dynamically-typed, #grails, #groovy

Groovy Order of Operations Gotcha: Ternary Operator vs. Left Shift

The code below perplexed the heck out of me until a co-worker helped me understand the problem.

List items = []
items << row.policy_no
items << row.insured_name
items << row.effective_date ? row.effective_date.format('MM/dd/yyyy') : 'not available'
items.each { item ->
    println item
}

No matter what I did, I could not seem to get the value for effective_date to format.

What is happening here is that Groovy is evaluating the left shift operator (<<) before the ternary operator (?:).  I was able to get this code to do what I wanted simply by wrapping the expression in parentheses.

items << row.effective_date ? row.effective_date.format('MM/dd/yyyy') : 'not available' )

My first impression is that this would seem to be a bug, or at least undesirable behavior on Groovy’s part. But there is probably a good reason for it that I’m not seeing.

#groovy, #left-shift, #order-of-operations, #ternary-operator

ACORD, enums and Groovy

ACORD publishes standards for information exchange by insurance and financial industries. As a part of their specifications, they provide lists (called lookups) of constant values (called type codes) for all kinds of items that insurance carriers, customers or users might need to communicate about.  For example, the lookup named OLI_LU_GENDER contains numeric values for genders. If two parties need to communicate about the rates for a policy to cover a male, age 35, non-smoker, then they can use an XML message that declares the applicant has:

<Gender tc="1"/>

where the tc=”1″ refers to the type code for ‘Male’.

As a developer writing web services that need to speak ACORD XML, it can be a tedious chore to have to keep referring to the ACORD manual to look up type codes. Fortunately, Java and Groovy make it easy to convert ACORD lookups into a library of enums.  With an IDE that does code completion, having the enums jar in your classpath is all you need to be able to work with ACORD lookups and type codes at a decent speed.  Simply start typing the name of a lookup (and most of the ones in the OLifE standard start with “OLI_LU_”) and the IDE prompts you with choices. Tab to select your lookup and hit “.” and then you’re prompted with a list of type codes. Easy!

enum OLI_LU_GENDER {

    OLI_GENDER_MALE(1, 'Male'),
    OLI_GENDER_FEMALE(2, 'Female')

    private final Integer typeCode
    private final String text

    OLI_LU_GENDER(Integer typeCode, String text) {
        this.typeCode = typeCode
        this.text = text
    }

    String getName() {
        return name()
    }

    Integer getTypeCode() {
        return typeCode
    }

    String getText() {
        return text
    }

    static def findByName(String searchArg) {
        return this.find { it.name == searchArg }
    }

    static def findByTypeCode(Integer searchArg) {
        return this.find { it.typeCode == searchArg }
    }

    static def findByText(String searchArg) {
        return this.find { it.text == searchArg }
    }

    static def findByTextIgnoreCase(String searchArg) {
        return this.find { it.text.equalsIgnoreCase(searchArg) }
    }

}

I realize that the example above breaks Java and Groovy naming conventions for classes. However, for recognition and usability I think that it’s more important to stick to the ACORD naming conventions and use the original name of the lookup for the name of the enum.

Here are some example usages:

    String renderAcordXml() {
        def xml = new StreamingMarkupBuilder().bind { builder ->
            mkp.xmlDeclaration()
            mkp.declareNamespace('xsd': 'http://www.w3.org/2001/XMLSchema',
                    '': 'http://ACORD.org/Standards/Life/2',
                    'xsi': 'http://www.w3.org/2001/XMLSchema-instance')
            TXLife('xsi:schemaLocation':
            'http://ACORD.org/Standards/Life/2 schema\\TXLife2.16.01.xsd'
            ) {
                TXLifeRequest() {
                    TransType(tc: OLI_LU_TRANS_TYPE_CODES.OLI_TRANS_NBSUB.typeCode, OLI_LU_TRANS_TYPE_CODES.OLI_TRANS_NBSUB.text)
                    OLifE {
                        Party(id: INSURED_PARTY_ID) {
                            Person() {
                                FirstName(data.firstName)
                                MiddleName(data.middleName)
                                LastName(data.lastName)
                                Gender(tc: OLI_LU_GENDER.OLI_GENDER_MALE.typeCode, OLI_LU_GENDER.OLI_GENDER_MALE.text)
                            }
                        }
                    }
                }
             }
          }
<g:select name="insuredGender"
             from="${OLI_LU_GENDER.values()}"
             optionKey="typeCode"
             optionValue="text" />
InsuranceQuote quote = new InsuranceQuote()
quote.applicantGender = OLI_LU_GENDER.findByTypeCode(params.insuredGender)
quote.validate()
quote.save()

I wish I could crowdsource the development of a complete enum jar for each ACORD standard, or at least share the one I’m creating, but the standards are not my intellectual property.  The best that I can do for you is to suggest that it should be possible, if you’re an ACORD member and have access to the standards documents, to write a script that can parse tabular lookup data out of the docs and write Groovy enum code from it.

#acord, #enums, #groovy

Bulk Insert With Grails

Importing data from a text or CSV file into a database with Grails seems like it should be a simple thing. As it turns out, however, some approaches perform dramatically better than others.

Simple GORM

Let’s assume that we’ve created a Grails domain class called MyDomain to represent the table we’re importing into, and a parseLine() method to parse one line of file data (CSV or whatever) into values. You might try something like this:

void loadTable(File file) {
file.eachLine() { line ->
MyDomain dom = new MyDomain(parseLine(line))
dom.save()
}
}

Unfortunately, you would find this code to run very slowly and you might even run out of memory. This is because Grails is caching at two different levels, causing locking and a potential memory leak. Read more about it:

So it’s better to try a different approach. There are many possibilities.

Call an External Tool

If your database platform offers a command line bulk import tool, you could simply call and execute it outside of Grails.

String command = """
db2 LOAD CLIENT FROM '/home/me/data.txt' OF ASC 
METHOD L
(1 9, 10 10, 11 19, 20 20, 21 26, 27 35, 36 71, 72 107, 108 127, 128 129, 
130 134, 135 138, 139 141, 142 144, 145 148, 149 149, 150 150, 151 155)
NULL INDICATORS (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
INSERT INTO MY_SCHEMA.MY_TABLE
STATISTICS NO
ALLOW NO ACCESS;
"""
command.execute()

But you may not have such a command line tool, or it may not be available to you in all environments. And it’s probably not a good idea to give your web server the authority to run database commands from the shell.

Groovy SQL

Another approach might be to bypass Grails’ domain classes and the underlying Hibernate persistence layer and instead use Groovy Sql.

void loadTable(File file) {
def db = grailsApplication.config.dataSource
def sql = Sql.newInstance(db.url, db.user, db.password, db.driver)
file.eachLine() { line ->
sql.execute( "insert into my_table (field0, field1, field2, ...) values (?, ?, ?, ...)".toString(), parseLine(line))
}
}

Or for better performance, in Groovy 1.8.1+ (Grails 2.0.1+), you can batch the inserts:

void loadTable(File file) {
def db = grailsApplication.config.dataSource
def sql = Sql.newInstance(db.url, db.user, db.password, db.driver)
sql.withBatch(20, "insert into my_table (field0, field1, field2, ...) values (:val0, :val1, :val2, ...)".toString() { preparedStatement ->
file.eachLine() { line ->
def fields = parseLine(line)
preparedStatement.addBatch(val0:fields[0], val1:fields[1], val2:fields[2], ...)
}
}
}

Hibernate StatelessSession

If you prefer to stick with GORM, you can. You’ll just need to compensate for Grails’ and Hibernate’s caching mechanisms, by pausing to clear the caches after every 20 records or so, as described in Burt Beckwith’s article on validation caching and Hibernate’s documentation on batch processing. Or instead of having to worry about clearing the Hibernate cache, you can open a stateless database session, which does not cache, for the duration of your import.

void loadTable(File file) {
StatelessSession session = sessionFactory.openStatelessSession()
Transaction tx = session.beginTransaction()
file.eachLine() {line ->
MyDomain dom = new MyDomain(parseLine(line))
session.insert(dom)
}
tx.commit()
session.close()
}

I have not benchmarked any of these approaches to see which performs the best. You will probably have your own reasons to prefer one over the other.

#grails, #groovy, #hibernate, #insert, #sql

Signature With Timestamp Using iText and Luna HSM


import java.security.KeyStore
import java.security.MessageDigest
import java.security.PrivateKey
import java.security.cert.Certificate
import com.itextpdf.text.pdf.PdfReader
import com.itextpdf.text.pdf.PdfStamper
import com.itextpdf.text.pdf.PdfSignatureAppearance
import com.itextpdf.text.pdf.PdfName
import com.itextpdf.text.Image
import com.itextpdf.text.Rectangle
import com.itextpdf.text.pdf.PdfEncryptor
import com.itextpdf.text.pdf.PdfWriter
import com.itextpdf.text.pdf.PdfSignature
import com.itextpdf.text.pdf.PdfDate
import com.itextpdf.text.pdf.TSAClient
import com.itextpdf.text.pdf.TSAClientBouncyCastle
import com.itextpdf.text.pdf.PdfPKCS7
import com.itextpdf.text.DocumentException
import com.itextpdf.text.pdf.PdfDictionary
import com.itextpdf.text.pdf.PdfString
import com.chrysalisits.crypto.LunaTokenManager



String hsmPartitionLabel = "Luna_partition_name"
String hsmPassword = "partition_password"
String hsmKeyLabel = "private_key_alias"
String hsmCertLabel = "certificate_alias"
String hsmCALabel = "CA_certificate_alias"
String timestampUrl = "URL_of_timestamp_server"
String ownerPassword = "PDF_owner_password"
String inFile = "path_to_unsigned_PDF"
String reason = "signature_reason"
String location = "signature_location"
String contact = "signature_contact_email"
String sealPath = "path_to_an_image_to_be_used_with_signature"
String outFile = "path_to_put_signed_PDF"

// Login to HSM
LunaTokenManager tm = LunaTokenManager.getInstance()
tm.Login(hsmPartitionLabel, hsmPassword)

// Dynamically load security providers
Class providerClass = Class.forName("com.chrysalisits.crypto.LunaJCAProvider")
java.security.Provider provider = (java.security.Provider)providerClass.newInstance()
java.security.Security.removeProvider(provider.getName())
java.security.Security.insertProviderAt(provider, 2)
providerClass = Class.forName("com.chrysalisits.cryptox.LunaJCEProvider")
provider = (java.security.Provider) providerClass.newInstance()
java.security.Security.removeProvider(provider.getName())
java.security.Security.insertProviderAt(provider, 3)

// This syntax gets an instance of a LunaKeystore
KeyStore ks = KeyStore.getInstance("Luna")
ks.load(null, null)
PrivateKey key = (PrivateKey) ks.getKey(hsmKeyLabel, null)
// We need to assemble the certificate chain manually because the HSM doesn't support the
// getCertificateChain method.
Certificate[] chain = new Certificate[2]
chain[0] = ks.getCertificate(hsmCertLabel)
chain[1] = ks.getCertificate(hsmCALabel)

// It seems necessary to load the file into the PdfReader this way to
// avoid a java.io.IOException in sun.nio.ch.FileChannelImpl on AIX.
byte[] content = new File(inFile).readBytes()
PdfReader reader = new PdfReader(content, ownerPassword.getBytes())
FileOutputStream fout = new FileOutputStream(outFile)
// Third param is PDF revision (char).
// Groovy thinks '' is a GString, so we have to be explicit and force it to char.
PdfStamper stp = PdfStamper.createSignature(reader, fout, ''.toCharacter().charValue(), null, true)
PdfSignatureAppearance sap = stp.getSignatureAppearance()
// Instead of reason and location, a graphic image will be rendered. Reason and
// location will still be shown in the signature properties.
sap.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC)
Image image = Image.getInstance(sealPath)
sap.setSignatureGraphic(image)
// sap.setVisibleSignature(new Rectangle(x-len, y-len, x-loc, y-loc), page, null for new fieldname)
// Coordinates begin from lower left. Units are 1/72 of an inch. 8.5 x 11 in == 612 x 792
sap.setVisibleSignature(new Rectangle(36, 36, 100, 100), 1, null)
sap.setCrypto(key, chain, null, PdfSignatureAppearance.WINCER_SIGNED)

PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, new PdfName("adbe.pkcs7.detached"))
dic.setReason(reason)
dic.setLocation(location)
dic.setContact(contact)
dic.setDate(new PdfDate(sap.getSignDate()))
sap.setCryptoDictionary(dic)

// This is estimated space for the signature itself.
int contentEstimated = 15000
HashMap exc = new HashMap()
exc.put(PdfName.CONTENTS, new Integer(contentEstimated * 2 + 2))
sap.preClose(exc)

// make the digest
InputStream data = sap.getRangeStream()
MessageDigest messageDigest = MessageDigest.getInstance("SHA1")
byte[] buf = new byte[8192]
int n
while ((n = data.read(buf)) > 0) {
messageDigest.update(buf, 0, n)
}
byte[] hash = messageDigest.digest()
Calendar cal = Calendar.getInstance()

// If we add a time stamp:
TSAClient tsc = null
String tsa_url = timestampUrl
// Our provider does not use userid and password; use
// TSAClientBouncyCastle(tsa_url, tsa_userid, tsa_password)
// if yours does.
tsc = new TSAClientBouncyCastle(tsa_url)

byte[] ocsp = null

// Create the signature
PdfPKCS7 sgn = new PdfPKCS7(cert.key, cert.chain, null, "SHA1", null, false)
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, cal, ocsp)
sgn.update(sh, 0, sh.length)
byte[] encodedSig = sgn.getEncodedPKCS7(hash, cal, tsc, ocsp)

if (contentEstimated + 2 < encodedSig.length)
throw new DocumentException("Not enough space")

byte[] paddedSig = new byte[contentEstimated]
System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length)
// Replace the contents
PdfDictionary dic2 = new PdfDictionary()
dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true))
sap.close(dic2)

tm.Logout()

println "Signed PDF saved as ${outFile}."

#adobe-cds, #digital-signature, #groovy, #hsm, #itext, #luna, #pdf, #timestamp

Signing a PDF with iText and a Luna HSM

After obtaining a trial of a Certificate for Adobe CDS, which is only delivered on a hardware storage module (HSM) or a USB key, I had some difficulties adapting the iText examples for signing a PDF to work with the API for the HSM. The code below is the result of trial-and-error. It is based on the examples provided for iText at http://itextpdf.com/book/examples.php#chapter12.

I have been told that there is a naming convention for certificates on the HSM that, if followed, makes the KeyStore.getCertificateChain() method usable: The labels for each certificate should all start the same way (in this case, it would be ‘mycert’) and followed by the suffix ‘–cert#’, where # is the number of the certificate in the chain. In this instance, it would be something like this:

  • mycert–cert0 (Adobe CDS certificate)
  • mycert–cert1 (GlobalSign CA for Adobe certificate)
  • mycert–cert2 (Adobe Root CA certificate)

If it works, that should make it unnecessary to create the certificate chan manually, as is done in this example.

This example uses Groovy, but it should translate almost directly to Java.


import java.security.KeyStore
import java.security.MessageDigest
import java.security.PrivateKey
import java.security.cert.Certificate
import com.itextpdf.text.pdf.PdfReader
import com.itextpdf.text.pdf.PdfStamper
import com.itextpdf.text.pdf.PdfSignatureAppearance
import com.itextpdf.text.pdf.PdfName
import com.itextpdf.text.Image
import com.itextpdf.text.Rectangle
import com.itextpdf.text.pdf.PdfEncryptor
import com.itextpdf.text.pdf.PdfWriter
import com.itextpdf.text.pdf.PdfSignature
import com.itextpdf.text.pdf.PdfDate
import com.itextpdf.text.pdf.TSAClient
import com.itextpdf.text.pdf.TSAClientBouncyCastle
import com.itextpdf.text.pdf.PdfPKCS7
import com.itextpdf.text.DocumentException
import com.itextpdf.text.pdf.PdfDictionary
import com.itextpdf.text.pdf.PdfString
import com.chrysalisits.crypto.LunaTokenManager



String hsmPartitionLabel = "Luna_partition_name"
String hsmPassword = "partition_password"
String hsmKeyLabel = "private_key_alias"
String hsmCertLabel = "certificate_alias"
String hsmCALabel = "CA_certificate_alias"
String ownerPassword = "PDF_owner_password"
String inFile = "path_to_unsigned_PDF"
String reason = "signature_reason"
String location = "signature_location"
String contact = "signature_contact_email"
String sealPath = "path_to_an_image_to_be_used_with_signature"
String outFile = "path_to_put_signed_PDF"

// Login to HSM
LunaTokenManager tm = LunaTokenManager.getInstance()
tm.Login(hsmPartitionLabel, hsmPassword)

// Dynamically load security providers
Class providerClass = Class.forName("com.chrysalisits.crypto.LunaJCAProvider")
java.security.Provider provider = (java.security.Provider)providerClass.newInstance()
java.security.Security.removeProvider(provider.getName())
java.security.Security.insertProviderAt(provider, 2)
providerClass = Class.forName("com.chrysalisits.cryptox.LunaJCEProvider")
provider = (java.security.Provider) providerClass.newInstance()
java.security.Security.removeProvider(provider.getName())
java.security.Security.insertProviderAt(provider, 3)

// This syntax gets an instance of a LunaKeystore
KeyStore ks = KeyStore.getInstance("Luna")
ks.load(null, null)
PrivateKey key = (PrivateKey) ks.getKey(hsmKeyLabel, null)
// We need to assemble the certificate chain manually because the HSM doesn't support the
// getCertificateChain method.
Certificate[] chain = new Certificate[2]
chain[0] = ks.getCertificate(hsmCertLabel)
chain[1] = ks.getCertificate(hsmCALabel)

// It seems necessary to load the file into the PdfReader this way to
// avoid a java.io.IOException in sun.nio.ch.FileChannelImpl on AIX.
byte[] content = new File(inFile).readBytes()
PdfReader reader = new PdfReader(content, ownerPassword.getBytes())
FileOutputStream fout = new FileOutputStream(outFile)
// Third param is PDF revision (char).
// Groovy thinks '' is a GString, so we have to be explicit and force it to char.
PdfStamper stp = PdfStamper.createSignature(reader, fout, ''.toCharacter().charValue(), null, true)
PdfSignatureAppearance sap = stp.getSignatureAppearance()
sap.setCrypto(cert.key, cert.chain, null, PdfSignatureAppearance.WINCER_SIGNED)
// Instead of reason and location, a graphic image will be rendered. Reason and
// location will still be shown in the signature properties.
sap.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC)
sap.setReason(reason)
sap.setLocation(location)
sap.setContact(contact)
Image image = Image.getInstance(sealPath)
sap.setSignatureGraphic(image)
// sap.setVisibleSignature(new Rectangle(x-len, y-len, x-loc, y-loc), page, null for new fieldname)
// Coordinates begin from lower left. Units are 1/72 of an inch. 8.5 x 11 in == 612 x 792
sap.setVisibleSignature(new Rectangle(36, 36, 100, 100), 1, null)
stp.close()

tm.Logout()

println "Signed PDF saved as ${outFile}."

#adobe-cds, #groovy, #hsm, #itext, #luna, #pdf