Archive for August, 2011

Logging in to Salesforce from ColdFusion

August 31, 2011

We have been working the last few months on an experiment with Salesforce.com. The code is some of our first to be written almost purely using CFScript and Hibernate. One of our challenges was communicating with Salesforce, in particular, their fairly new REST API. Their API documentation is pretty good, but the one area that was somewhat lacking at the time was how to log in. Once we figured it out, the solution was rather simple, it just took a little while to find the right combination of parameters.

We hope to eventually release our entire CFC for talking with Salesforce, which has a lot of convenience methods to get, set, upsert, and describe Salesforce objects, but for now, here is our code to log in and get an object.

public boolean function logIn() {
	if (variables.token EQ '' OR arguments.force) {
		local.http = new Http(url=variables.salesforceInstance & '/services/oauth2/token',method='post');
		local.http.addParam(type='formField',name='grant_type',		value='password');
		local.http.addParam(type='formField',name='client_id',		value=variables.CLIENTID);
		local.http.addParam(type='formField',name='client_secret',	value=variables.CLIENTSECRET);
		local.http.addParam(type='formField',name='username',		value=variables.USERNAME);
		local.http.addParam(type='formField',name='password',		value=variables.PASSWORD);
		local.http.addParam(type='formField',name='format',		value='json');

		local.httpSendResult = local.http.send();
		local.httpResult = httpSendResult.getPrefix();

		if (StructKeyExists(local.httpResult, "responseHeader")
				&& StructKeyExists(local.httpResult.responseHeader, "status_code")
				&& local.httpResult.responseHeader.status_code EQ 200) {
			local.stringResult = gatherResponseString(local.httpResult);
			//variables.json is an in-house wrapper around 'org.json.simple.JSONValue'. DeserializeJSON may work for you as well
			local.json = variables.json.toObject(local.stringResult);
			variables.token = local.json.access_token;
		} else {
			variables.token = '';
			local.errorString = gatherResponseString(local.httpResult);
			if (StructKeyExists(local.httpResult, "ErrorDetail")) {
				local.errorString &= ", detail: " & local.httpResult.ErrorDetail;
			}
			throw (message='Unable to authenticate to SalesForce: ' & local.errorString, type='lampo.salesforce.loginerror');
		}
	}
}
public any function getObject(required string sfid, required string type, array fields) {
	local.url=variables.salesforceInstance & '/services/data/v' & variables.apiVersion &'/sobjects/'&arguments.type&'/' & arguments.sfid;

	if (NOT isNull(fields) AND ArrayLen(fields) GT 0) {
		local.url = local.url & '?fields=' & ArrayToList(fields);
	}

	return makeJsonGetCall(url=local.url,errorMessage='Unable to find ' & arguments.type & ' with sfid ' & arguments.sfid);
}

/*************** HELPER FUNCTIONS ********************/
private function gatherResponseString(required any httpresult) {
	if (IsBinary(httpResult.FileContent)) {
		return CharsetEncode(httpResult.FileContent, httpResult.CharSet);
	} else {
		return trim(httpResult.FileContent);
	}
}

private any function makeJsonGetCall(required string url, required string errorMessage) {
	local.httpResult = makeCall(arguments.url,'GET',{});

	if (local.httpResult.responseHeader.status_code EQ 200) {
		local.fromJson = variables.json.toObject(gatherResponseString(local.httpResult));
		return local.fromJson;
	} else {
		throw (message=arguments.errorMessage & ' (status: '& local.httpResult.responseHeader.status_code &')',type='lampo.salesforce.objectNotFound')	;
	}
}
private any function makeCall(required string url, required string method,required struct params) {
	local.http = new Http(url=arguments.url,method=arguments.method);
	for (paramType in params) {
		for (paramKey in params[paramType]) {
			if (paramType NEQ 'header' or paramType neq 'Authorization') {
				local.http.addParam(type=paramType,name=paramKey,value=params[paramType][paramKey]);
			}
		}
	}
	local.http.addParam(type='header',name='Authorization',value='OAuth ' & variables.token);
	local.httpSendResult = local.http.send().getPrefix();

	if (local.httpSendResult.responseHeader.status_code EQ 401) {
		throw (message = 'Unable to log into SalesForce: ' & local.httpSendResult.responseHeader.status_code & '('&gatherResponseString(local.httpSendResult)&')',detail=gatherResponseString(local.httpSendResult),type='lampo.salesforce.loginFailure');
	} else {
		return local.httpSendResult;
	}
}

Coldfusion 9 ORM, Caching and Autocommit

August 22, 2011

We have been using ColdFusion 9 for a few months now. With all new code that is developed, we have been abandoning <CFQUERY> in favor of CF9’s ORM, which is based on Hibernate. This is a great technology that lets us focus more on work that matters, instead of coding lots of repetitive SQL queries. But, as with any new technology, there are some gotchas that developers have to be aware of. Two such gotchas have come up recently on projects I have worked on, and I am going to share those with you today.

Setup

For this simple example, we are going to use a very basic table.

create table dbo.person (
	[id] [int] IDENTITY(1,1) NOT NULL,
	[first_name] [varchar](128) NOT NULL,
	[last_name] [varchar](128) NOT NULL,
);

In traditional ColdFusion, if you wanted to update a person, you would perform a with an update statement, like so:

update person set first_name=
where id =

In ColdFusion 9 with ORM, you have an object that represents a person, as defined in Person.cfc. Note that I will be using 100% CfScript for the rest of this article.

component persistent="true" table="person" {
	property name="id" fieldtype="id" generator="native";
	property name="firstName" column="first_name";
	property name="lastName" column="last_name";
}

To update the person, you would write something like the following:

local.person = EntityLoadByPk('Person',arguments.personId);
local.person.setFirstName(arguments.newFirstName);
EntitySave(local.person);

So far, this is all basic ColdFusion ORM. Let’s look at some things you need to be aware of as your application grows in complexity.

Gotcha 1 : Caching

Consider the following MxUnit code:

public void function testFunction1() {
	local.person = EntityLoad('Person',{firstName='John',lastName='Doe'},true);
	local.person.setFirstName('Jane');
	otherFunction1();
}
private void function otherFunction1() {
	local.person = EntityLoad('Person',{firstName='John',lastName='Doe'},false);
	assertEquals(1,local.person.size());
	assertEquals('Jane',local.person[1].getFirstName());
}

In function otherFunction1, we have asked Hibernate to give us John Doe a second time. One would expect this to return a fresh copy of John Doe, but actually it has returned a reference to the same person as inside testFunction1. The query is indeed performed on ‘John Doe’, but in the scope of the current request, that maps to an object already in memory, one that has a new first name. You can read a little more about this form of caching in the CF docs.

It is important to also see what happens when you use EntityReload. Since both variables point to the same instance from the cache, an EntityReload on will will reload the other. Consider the following:

public void function testFunction2() {
	local.person = EntityLoad('Person',{firstName='John',lastName='Doe'},true);
	local.person.setFirstName('Jane');
	otherFunction2();
	assertEquals('John',local.person.getFirstName());
}
private void function otherFunction2() {
	local.person = EntityLoad('Person',{firstName='John',lastName='Doe'},true);
	assertEquals('Jane',local.person.getFirstName());
	EntityReload(local.person);
	assertEquals('John',local.person.getFirstName());
}

Gotcha 2: Autocommit

By default, Hibernate is configured to commit any changed objects. In our above example, the EntitySave(local.person) is completely optional – at the end of the request, the changes to the person will be persisted to the database. What’s bad about this is that in the pre-ORM world, developers in ColdFusion are used to explicitly updating the database via____ or some other framework. You would think that the equivalent would be calling EntitySave.

This recently bit me when I was validating some data before writing it back to the database. We have a form that is used to update contact information, and allows for our users to add new types of contacts to a person (such as billing e-mail, toll-free number, etc). There are some rules involved, one being that certain people must have one and only one billing e-mail address. Instead of validating the form and the database separately, the code I was working on converted the new contact field from the form into a new database object (via EntityNew), added it to the list of existing contact fields, and then ran the validation on the objects. If validation failed, the idea was to present the user with an error so they could correct it, and new or updated information would not make it to the datbase. Instead, Hibernate went ahead and saved all changes to the database, and allowed the error message to be displayed.

To work around this, there are a few things you can do:

  1. Don’t mess with objects unless you want your changes to always be committed
  2. Turn off autocommit by adding <property name=”connection.autocommit”>true</property> to your hibernate.xml file.
  3. If you want to back out any changes you have made to an object, call EntityReload(local.myEntity) to discard changes
  4. Use transactions
I will elaborate a little bit on #4. Assume the database has John Doe in it as you look at the following MxUnit code:
public void function testFunction3() {
	transaction {
		local.person = EntityLoad('Person',{firstName='John',lastName='Doe'},true);
		local.person.setFirstName('Jane');
		transactionrollback();
		assertEquals('Jane',local.person.getFirstName());
	}
	otherFunction3();
	assertEquals('Jane',local.person.getFirstName());
}

private void function otherFunction3() {
	transaction {
		local.person = EntityLoad('Person',{firstName='John',lastName='Doe'},false);
		assertEquals(1,local.person.size());
		assertEquals('John',local.person[1].getFirstName());
	}
}

Note that transactionRollback() does not rollback the change to the object, but it does appear to invalidate the cache. The second method gets a fresh copy from the database.

These gotchas can be very good things, but if you develop your application without being aware of them, you will be very surprised when data starts appearing in your database that you thought you had thrown away.

All your base are visible to us

August 9, 2011

Here’s a little safety switch for utility web pages. I have a quick-n-dirty, static html file I use simply for submitting a form to test certain features of our site. I typically use this only in my local development but occasionally in other environments. I set it by un-commenting a desired <base> element like so:

<head>
  <title>Post Test Form</title>
    <!--
    <base href="https://local.example.com/path/to/feature" />
    <base href="http://qa.example.com/path/to/feature" />
    <base href="http://production.example.com/path/to/feature" />
    -->
    <base href="http://test.example.com/path/to/feature" />
    …

This typically works well for me, but I have at times changed the ‘active’ base element and forgotten. If only the base element were rendered somewhere in the browser. I could reveal the base element with CSS (display: block), but the base element has no content, only an href attribute.

Generated content to the rescue:

    /* first reveal the 'head', hide all the head's ancestor elements, un-hide the base element */
    head {display:block;}
    head * {display: none;}
    head base {display: block;}

    /* use a pseudo-class that enables generated content */
    head base:after {
        border: 1px solid ActiveBorder;
        background-color: ActiveCaption;
        color: CaptionText;
        display: block;
        /* display the value of the href attribute */
        content: attr(href);
        padding: 1ex;
        font-family: monospace;
    }

Now, the top of my static page has a block that displays the current ‘active’ base element. While I’m at it, how about one more little tweak to warn me when I’m in production?

    head base[href="http://production.example.com/path/to/feature"]:after {
        background-color: #FEE;
        color: #F00;
        font-weight: bold;
    }

Now, my view into my base element will show bold and red if I’ve un-commented the production base element.