Matthew Woodward // * CFML, Grails, and Java Developer
* Principal IT Specialist, US Senate
* Open BlueDragon Steering Committee Member
* All-Around Geek
Grails is a Java- and Groovy-based web framework that is built for speed. First-time developers are amazed at how quickly you can get a page-centric MVC web site up and running thanks to the scaffolding and convention over configuration that Grails provides. Advanced web developers are often pleasantly surprised at how easy it is to leverage their existing Spring and Hibernate experience.
"Getting Started with Grails" brings you up to speed on this modern web framework. Companies as varied as LinkedIn, Wired, Tropicana, and Taco Bell are all using Grails. Are you ready to get started as well?
The second edition of "Getting Started with Grails" in now available for free on InfoQ, or you can buy the print version for only $22.95. The first edition was great so I'm really looking forward to reading the update.
I'm working on an application with a friend of mine and we're taking full advantage of all the great new functions and features in Open BlueDragon. One of the new functions I wasn't sure I'd use all that much is QueryRun(), which lets you run a query in a CFSCRIPT block like this:
<cfscript> QueryRun(datasource, sqlStatement, queryParams); </cfscript>Let's look at a concrete example. Assume a table called "user" with fields of id, email, first_name, and last_name, and a datasource named "myDSN". To pull all the users, you'd do this:
<cfscript>
getUsersSQL = "SELECT id, email, first_name, last_name FROM user";
users = QueryRun("myDSN", getUsersSQL);
</cfscript>
Now if you want to pull a specific user--let's say by email--you can parameterize your queries by using an array of structs that represent the CFQUERYPARAM tags, where the key in each struct is the attribute name from CFQUERYPARAM, and the value for each key is the value of the CFQUERYPARAM attribute. Things start looking a bit Java-esque with the question marks, but here's how that works (and note use of the newly added implicit array and struct notation):
<cfscript>
getUsersSQLParams = [{value = "matt@mattwoodward.com", cfsqltype = "cf_sql_varchar", maxlength = 100}];
getUsersSQL = "SELECT id, email, first_name, last_name FROM user ";
getUsersSQL &= "WHERE email = ?";
users = QueryRun("myDSN", getUsersSQL, getUsersSQLParams);
</cfscript>
Let's go over what's going on here. First, in the struct contained in the getUsersSQLParams array, you can see the familiar attributes of value, cfsqltype, and maxlength from CFQUERYPARAM. Next, notice the ? in the SQL statement. Any question marks in your SQL statement will get replaced in the order in which they appear by the structs contained in the SQL parameters array. So in this case, that ? in the SQL statement essentially becomes a CFQUERYPARAM with the key/value pairs in the struct defined two lines above. To reinforce this point, let's look at an example pulling a user by first name and last name:
<cfscript>
getUsersSQLParams = [{value = "Matt", cfsqltype = "cf_sql_varchar", maxlength = 100},
{value = "Woodward", cfsqltype="cf_sql_varchar", maxlength = 100}];
getUsersSQL = "SELECT id, email, first_name, last_name FROM user ";
getUsersSQL &= "WHERE first_name = ? AND last_name = ?";
users = QueryRun("myDSN", getUsersSQL, getUsersSQLParams);
</cfscript>
In that case I have two parameters in my SQL statement represented by two question marks, the first of which is replaced by the first struct in the SQL parameters array (corresponding to first_name), and the second is replaced by the second struct in the array (corresponding to last_name). While working on this application I came across an added bonus with this way of doing things that I didn't really consider when the feature was first added to Open BlueDragon. Since your query parameters are stored in an array of structs as opposed to being added to each query as individual CFQUERYPARAM tags, you can re-use the query parameters across multiple queries. When would this come in handy? Consider basic CRUD operations, specifically inserts and updates. In many cases the only difference between an insert and update is a "WHERE id = ?" clause, but using CFQUERY you wind up having to repeat a ton of CFQUERYPARAM tags between the two queries. By using QueryRun() and the array of structs that represent the query parameters, these parameters can be reused between an insert and an update operation. This example is fairly trivial since the table is so small, but you can imagine how much redundancy you save on large tables.
<cffunction name="save" access="public" output="false" returntype="void">
<cfargument name="user" type="User" required="true" />
<cfscript>
var commonSQLParams = [{value = arguments.user.getEmail(), cfsqltype = "cf_sql_varchar", maxlength = 100},
{value = arguments.user.getFirstName(), cfsqltype = "cf_sql_varchar", maxlength = 100},
{value = arguments.user.getLastName(), cfsqltype = "cf_sql_varchar", maxlength = 100}];
// if the ID is 0 do an insert, otherwise do an update
if (arguments.user.getID() == 0) {
var insertUserSQL = "INSERT INTO user (email, first_name, last_name) ";
insertUserSQL &= "VALUES (?, ?, ?)";
var insertUser = QueryRun("myDSN", insertUserSQL, commonSQLParams);
} else {
ArrayAppend(commonSQLParams, {value = arguments.user.getID(), cfsqltype = "cf_sql_integer"});
var updateUserSQL = "UPDATE user SET email = ?, first_name = ?, last_name = ? ";
updateUserSQL &= "WHERE id = ?";
var updateUser = QueryRun("myDSN", updateUserSQL, commonSQLParams);
}
</cfscript>
</cffunction>
Since the update statement only needed the one additional WHERE parameter of the ID, I can just append that to the end of the SQL parameters array I created from the data in the user object and re-use the rest of the parameters for both queries. Personally I think that's pretty darn slick, and although at first the ? notation may seem a bit odd at first (at least if you aren't used to doing things that way in Java), you wind up with some very concise code when using QueryRun() due to the ability to reuse the SQL parameters across multiple queries.
Today sees the release of some much awaited support for both implicit array/struct creation and a whole host of operators. The cfscript and expression parsing engines have been rewritten, switching from the javacc based parsers to a cleaner and leaner ANTLR based one.
Check out all the details on the OpenBD blog!
I was installing ColdFusion 8.01 on a new Windows Server 2003 VM yesterday, and since the application that will be running on this box needs access to network shares I had our network admins create a new domain service account under which to run ColdFusion. This is pretty commonplace for a lot of our apps, but for some reason when I switched the CF service to run under the service account I started getting a 500 error with the following details when any .cfm page was hit:
java.lang.NullPointerException
at jrun.servlet.JRunRequestDispatcher.invoke(JRunRequestDispatcher.java:285)
at jrun.servlet.ServletEngineService.dispatch(ServletEngineService.java:543)
at jrun.servlet.jrpp.JRunProxyService.invokeRunnable(JRunProxyService.java:203)
at jrunx.scheduler.ThreadPool$ThreadThrottle.invokeRunnable(ThreadPool.java:428)
at jrunx.scheduler.WorkerThread.run(WorkerThread.java:66)
This was even after I had assigned "full control" rights to the service account user to the application directory.
I asked around a bit since I'd never seen this before, and Dave Watts came up with the solution. (Thanks Dave!) In this case I logged onto the server via remote desktop using the service account, at which point I was forced to change my password. After I changed my password, the remote desktop login didn't proceed because that user didn't have RDP rights on the server. I didn't think that was an issue, but Dave suggested that there might be a problem if the service account didn't have a local profile created on the server, which of course happens the first time a user logs in.
I gave the service account RDP rights, logged in as that user, the local profile was created, and everything works now. So if you see null pointer errors when trying to run CF using a service account, this might be your problem.Might the trademark "NBC" be retired and the TV network become just another cog in a large, empty capitalist apparatus -- one that plops out leisure-time product with the slick, chilly efficiency of an assembly line? It's possible that Comcast could be even more tightfisted an owner than GE and that NBC might be the first network to prove that the whole idea of broadcast networks really is over. It could prove it by dying.
One of the networks has to be the first to go, and the most likely candidate is NBC. I was watching "Modern Family" the other night and one of the storylines (if you can call it that on a 22-minute sitcom) was about how no one in the house other than the Dad knows how to use the remote. (Tired gag, but well done in this case.)
So Dad is trying to convince Daughter to let him teacher her how to use the remote to show up Mom who says no one can learn it. Daughter exclaims, "Dad, this is stupid. I watch TV on my computer! Why do I have to learn this?"
It was said in passing but I think that pretty much sums up the future. People like myself who watch TV on the network's schedule, even when you take DVRs into account, are a dying breed and so are the networks. I think my eyes will be really opened up to this when my Moxi arrives this week and I hook it into PlayOn. Even if I watch TV shows being served *from* a computer I don't really enjoy watching them *on* a computer. ;-)
This came up yesterday in a discussion with a friend of mine. He had two arrays and he wanted to see if they were identical in terms of order and value of the elements. His first thought was to loop through the arrays and compare each element, but since CFML arrays are really Java arrays, there's a much simpler way.
<cfscript> arrray1 = ArrayNew(1); array1[1] = "foo"; array1[2] = "bar"; array2 = ArrayNew(1); array2[1] = "foo"; array2[2] = "bar"; arraysAreEqual = array1.equals(array2); </cfscript>The equals() method on the array returns a boolean and tells you whether or not the arrays are identical in terms of the values and ordering of the array elements. Note that if the values are identical but in a different order, the arrays will not be considered equal, so sort before doing this test if there's a chance things might be in a different order between the two arrays.
A while ago, YouTube launched a simple demo of an HTML5-based video player. Recently, we published a blog post on our pre-spring cleaning effort and your number one request was that YouTube do more with HTML5. Today, we're introducing an experimental version of an HTML5-supported player.HTML5 is a new web standard that is gaining popularity rapidly and adds many new features to your web experience. Most notably for YouTube users, HTML5 includes support for video and audio playback. This means that users with an HTML5 compatible browser, and support for the proper audio and video codecs can watch a video without needing to download a browser plugin.
Two words: Freakin'. Awesome. I probably wouldn't care so much if Flash didn't suck such copious amounts of ass on 64-bit Linux, but I can't wait to give this a shot.