Upgrading a Grails 2 application to Grails 3

Upgrading a Grails 2 application to Grails 3

Limited documentation made this upgrade a challenge. Here's what you need to know before you upgrade to Grails 3.

Upgrading a Grails 2 application to Grails 3
Image by : 

opensource.com

x

Get the newsletter

Join the 85,000 open source advocates who receive our giveaway alerts and article roundups.

I’m a huge fan of Grails. For me, the only real downside to building something in Grails is that the documentation tends to move quite a bit more slowly than the code. This is especially true for the learning materials that exist outside the reference documentation. So far I’ve been able to find only two books specifically oriented to Grails 3: Practical Grails 3: A hands-on guide to Grails (which may also be titled Grails 3: A Practical Guide to Application Development), and Grails 3 Step by Step. And since I have a number of Grails 2 applications that need to be moved to Grails 3, what I really need is a guide to Grails 3 for Grails 2 developers.

The closest I’ve been able to get to that guide is the Grails 3.0.x guide for upgrading, which is very brief and unfortunately covers only the broad strokes necessary since Grails 3.3.x continues to move on from Grails 3.0.x.

So, having spent a frustrating, but ultimately successful, ten days getting my first Grails 3 upgrade done, I thought it might be useful to pass on some of the lessons I’ve learned.

Creating and populating the Grails 3 application folder

Let’s begin with the basic steps outlined in the Grails 3.0.x guide for upgrading. This bears careful and thorough reading and re-reading, especially the sections File Location Differences, New Files Not Present in Grails 2.x, and Files Not Present in Grails 3.x. I blew through this part a little too quickly the first time (being totally honest, the first three or four times), and two things I missed were:

grails-app/conf/UrlMappings.groovy has moved to grails-app/controllers
grails-app/conf/BootStrap.groovy has moved to grails-app/init

I’ll leave the process of discovery of those changes—given I didn’t notice these moves in the documentation—to the reader’s imagination…

The next section of this page is entitled 3.1 Upgrading Plugins. As far as I can tell, this was meant more to assist users of plugins to upgrade those plugins from Grails 2 to Grails 3 than to assist plugin developers. Therefore, this didn’t apply to me, as now there are Grails 3.3.x versions of the three non-standard plugins I use (Spring Security Core, Spring Security UI, and Mail). Nevertheless, there is some good basic information here about how Grails 3 uses Gradle, the nice Groovy-based build tool, so I suggest reading it in any case.

Finally, the patient reader arrives at 3.2 Upgrading Applications. Here is where the fun starts. The documentation states that a good way to start is to first "create a new Grails 3 application using the 'web profile.'" Hmmm, I hear you thinking, what is a "web profile?" To answer that, I suggest reading the latest Grails Quick Reference section on Application Profiles.

Ok, you’re back from that page and you’re a bit bemused? I bet you didn’t read all the way down to 6.4 Understanding Profiles… All right, you read that too, and now you’re even more overwhelmed? To cut short the silliness, I like to think of the Grails 3 "web profile" as the configuration closest to the old standard Grails 2 configuration. In particular, this profile generates GSP (Groovy Server Page) views that all we Grails old-timers know about.

Back to the upgrade instructions… which suggest the following:

grails create-app myapp
cd myapp

To which I say, “Hold on a minute.” Let’s say you’re upgrading this old CMS (customer management system) written in Groovy 2 by the good folks at ancientware.com (which was not a registered domain name when I wrote this). That application might have been created back in the day using a command like:

grails create-app com.ancientware.cms

This would have created a project folder called cms, and all the domain and controller classes would appear in

cms/grails-app/domain/com/ancientware/cms

and

cms/grails-app/controllers/com/ancientware/cms

respectively (also services, tag libs, etc.). The point is that the "cms" package is created in the “ancientware.com" domain (thanks to this comment on StackOverflow for elucidating this practice), and then all classes are defined within that Groovy/Java package structure.

So use the same create command to generate the same package structure. Or, if you’re like me and you didn’t like the original package name, this is a good moment to begin that transition as well. Let's say you noticed that customer management systems are these days called "customer relationship management" systems or CRMs. So you're going to create the new version with this command:

grails create-app com.ancientware.crm

Following on, we next see that the upgrade guide recommends migrating resources as follows:

# first the sources
cp -rf ../old_app/src/groovy/ src/main/groovy
cp -rf ../old_app/src/java/ src/main/groovy
cp -rf ../old_app/grails-app/ grails-app
# then the tests
cp -rf ../old_app/test/unit/ src/test/groovy
mkdir -p src/integration-test/groovy
cp -rf ../old_app/test/integration/ src/integration-test/groovy

Where "old_app" would be "cms" in our case. Sharp-eyed readers might also have noticed that the above instructions create a too-deep file structure. For our hypothetical example, we should do the following (after issuing the create-app command):

cd crm
# first the sources
cp -rf ../old_app/src/groovy/* src/main/groovy
cp -rf ../old_app/src/java/* src/main/groovy
cp -rf ../old_app/grails-app/* grails-app
# then the tests
cp -rf ../old_app/test/unit/* src/test/groovy
mkdir -p src/integration-test/groovy
cp -rf ../old_app/test/integration/* src/integration-test/groovy

Note the asterisks appended to the folder names above.

This is a good moment to move UrlMappings.groovy and BootStrap.groovy to their respective new homes.

The build.gradle File and JQuery

Step 3 in the upgrade guide is pretty self-explanatory, if somewhat terse. Basically, it’s time to look at what was in the Grails 2 BuildConfig.groovy file (plugins, for example) and figure out what needs to be put into build.gradle. Based on my experience, I would suggest adding as little as possible here. If you know you need a plugin (like Spring Security Core, for instance), then you’re going to have to add it. But things like famfamfam, maybe not. I ended up changing the version number (though perhaps I should have left it as "0.1") and added the following lines to the dependences section:

    compile 'org.grails.plugins:spring-security-core:3.2.1'
    compile 'org.grails.plugins:spring-security-ui:3.1.2'
    compile 'org.grails.plugins:grails-markdown:3.0.0'
    compile fileTree(dir:'lib', include:'*.jar')

One really important thing to be aware of is that Grails 3 doesn't use plugins for JQuery and JQuery-UI. Instead, the actual JQuery stuff is managed by the Grails 3 asset pipeline. And if your Grails 2 is old enough that it is using resources rather than the asset pipeline, then here's another new thing to learn about.

This StackOverflow article offers a nice suggestion as to how to get JQuery working with Grails 3.0. Basically, the suggestion is to include these two lines in your GSP header:

    <asset:javascript src="application.js"/>
    <asset:stylesheet src="application.css"/>

and that application.js and application.css will include the relevant JavaScript and CSS.

Well, almost. I needed to get both JQuery and JQuery-UI in place, so my grails-app/assets/javascripts/application.js file looks like this:

// This is a manifest file that'll be compiled into application.js.
//
// Any JavaScript file within this directory can be referenced here
// using a relative path.
//
// You're free to add application-wide JavaScript to this file,
// but it's generally better to create separate JavaScript files as needed.
//
//= require jquery-2.2.0.min.js
//= require jquery-ui.min.js
//= require_tree .
//= require_self

if (typeof jQuery !== 'undefined') {
    (function($) {
        $('#spinner').ajaxStart(function() {
            $(this).fadeIn();
        }).ajaxStop(function() {
            $(this).fadeOut();
        });
    })(jQuery);
}

The two lines above that provide the JQuery and JQuery-UI start with //= require jquery.

There are other options, including linking to a content delivery network (CDN) or actually including the libraries in the header directly. Similarly, it’s possible to pull in various CSS files into application.css.

Application.groovy or Application.yml?

Step 4 mentions, in a hasty way, the choice of application.groovy or application.yml. Eventually—after some trial and error—I decided to go with adjusting a brand-new application.yml rather than trying to make application.groovy work as an amalgam of the Grails 2 Config.groovy and DataSource.groovy. In my case, the only thing I had to change were the lines that defined my database connection, under datasource: and environments:. In particular, the lines

    driverClassName: org.h2.Driver
    username: sa
    password: ''

and

    development:
        dataSource:
            dbCreate: create-drop
            url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
    test:
        dataSource:
            dbCreate: update
            url: jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
    production:
        dataSource:
            dbCreate: none
            url: jdbc:h2:./prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE

needed to be adjusted to work with my PostgreSQL instance (driver, username, password, jdbc…).

The other thing I wonder about in my application.yml file are the two lines at the top of the file:

    codegen:
        defaultPackage: com.ancientware

This Grails 3 tutorial discusses the matter, but somehow after reading it, I don’t feel satisfied. It seems to me that the default package should be "com.ancientware.crm" but so far, I have had no obvious problems created by this.

Dealing with the package name change (if done)

If like me, you decide to change the package name, there are basically two problems to solve. The first is the directory structure: Following our example, if the domain classes were originally in

grails-app/domain/com/ancientware/cms

then that path needs to be renamed to

grails-app/domain/com/ancientware/crm

Same for controllers, services, taglibs, tests…

Beyond that, all "includes" need to be changed – package statements in any .groovy file, any

<%@ page import="com.ancientware.cms.Client" %>

lines in any views, and so on.

At this point, it’s a good idea to review steps 6, 7, and 8 of the upgrade guide. But things don’t end there…

Changes in GSPs

My first migrated application is a reporting site for clients and so is primarily read-only from their perspective. All the data maintenance is done by a few trusty clerks. That being the case, the Grails 2-generated CRUD was pretty effective as a starting point for the data maintenance framework.

The create.gsp files generally needed a particular type of change to work. In the Grails 2 files, the line

<g:form url="[resource:client, action:'save']"  >

had to be replaced with something like this:

<g:form resource="${this.client}" method="POST">

The former lines did not seem to actually cause the save to occur, nor did they redirect to the show.gsp page. I speculate that this is due to the absence of the "method" parameter.

I changed the edit.gsp files similarly. The Grails 2 lines like

<g:form url="[resource:client, action:'update']" method="PUT" >

were replaced with something like this:

<g:form resource="${this.client}" method="PUT">

The former lines seemed to result in a 405 error.

Strangely, the "resource" parameter is not documented, even in the relevant Grails 3.3 GSP documentation. So how did I learn about this? Simple—I built a sample application from the ground up, created a domain class, and generated controllers and views for that domain class, and the views used the "resource" parameter.

The edit.gsp also needed another change to work correctly (otherwise they 405ed). Lines like

<g:actionSubmit class="save" action="update" value="${message(code: 'default.button.update.label', default: 'Update')}" />

had to be replaced with

<input class="save" type="submit" value="${message(code: 'default.button.update.label', default: 'Update')}" />

Again, the source of inspiration was the sample application.

The show.gsp once again required similar treatment. Lines like

<g:form url="[resource:client, action:'delete']" method="DELETE">

were replaced with

<g:form resource="${this.client}" method="DELETE">

and lines like

<g:actionSubmit class="delete" action="delete" value="${message(code: 'default.button.delete.label', default: 'Delete')}" onclick="return confirm('${message(code: 'default.button.delete.confirm.message', default: 'Are you sure?')}');" />

were replaced with

<input class="delete" type="submit" value="${message(code: 'default.button.delete.label', default: 'Delete')}" onclick="return confirm('${message(code: 'default.button.delete.confirm.message', default: 'Are you sure?')}');" />

Aside from that, for whatever reason, my JQuery-UI-related styling changed as well, and I had to insert a small <style> block at the beginning of pages using that library.

Whew! That’s it, the index.gsp and _form.gsp files were fine as was. I should mention for completeness that the Grails 3 CRUD uses a new tag library rather than GSP forms to achieve the rendering of the instances, but the old form technology seems to work fine.

Changes in controllers, domains, services, and taglibs

Fortunately, the controllers didn’t require as many changes as the GSPs did. The big surprise for me was an apparent change in the meaning of the @Transactional directive.

In Grails 2, I placed

@Transactional(readOnly = true)

at the beginning of each CRUD controller, and then where data updates were allowed (for example, the save() method), I placed

@Transactional

This did not work at all in Grails 3. There I found I had to place

@Transactional(readOnly = true)

only in front of the read-only methods (for example, the show() method).

As for the domains, services, and taglibs, no changes were required.

Spring Security

Neither Spring Security Core nor UI worked at this point, so I ended up reinitializing both. For Core, this command:

grails s2-quickstart com.ancientware.crm User Role

did the job. Before running it, I saved my old User.groovy domain class as I had added some fields to it. Some time back I decided to use a different name for the PostgreSQL tables holding User, Role, and UserRole data, so I had to insert the table name mapping into the new domain class definitions.

As for UI, I used:

grails s2ui-override auth
grails s2ui-override layout
grails s2ui-override register com.ancientware.crm.RegisterController
grails s2ui-override register com.ancientware.crm
grails s2ui-override registrationcode com.ancientware.crm
grails s2ui-override securityinfo com.ancientware.crm

I don’t have ACLs enabled, so I skipped over that part.

I’m not fully comfortable that my static rules are correct, but I added the following to grails-app/conf/application.groovy (yes, Spring Security does not use application.yml) and I am in the process of testing:

    [pattern: '/error/**',       access: ['permitAll']],
    [pattern: '/login',          access: ['permitAll']],
    [pattern: '/login/**',       access: ['permitAll']],
    [pattern: '/logout',         access: ['permitAll']],
    [pattern: '/logout/**',      access: ['permitAll']]

I also put the configuration stuff for the mail plugin in application.groovy. I haven’t tested it yet.

And One Last Thing - Tomcat Compatibility

After I had the application up and running, we discovered an interesting incompatibility with Tomcat7 (the newest version available on our older server).  I had some code that delivered results in .CSV format using the following Groovy syntax:

response.setHeader "Content-disposition", "attachment; filename=rcCandidate.csv"
response.contentType = 'text/csv'
response.outputStream << converted
response.outputStream.flush()

It turns out that third line, appending the contents of converted to the output stream, created a vast problem for Tomcat7.  The solution, provided by a very kind person on StackExchange, was to wrap the last two lines in separate static methods and annotate them with @CompileStatic.  For more detailed info see the relevant discussion on StackExchange.

That’s it!

Conclusions

First, despite the thinness in parts of the documentation, I was able to get to a working Grails 3 version of my application through a lot of trial and error, especially by creating a new sample application and studying the differences between it and my old code.

Second, I’m pretty happy with the lack of major changes required, despite the really large differences between late Grails 2 vintage applications and Grails 3.3.3 (the current latest version as of this writing).

Third, I need to do a lot of testing and generally exercising the system now, and try to nail down those last few uncertainties along the way.

Fourth, and to me foremost, I still really like Grails. Developers talk about a "batteries included" implementation for certain programming languages and frameworks; to me, Grails is "batteries, battery factory, lithium mines, and full regulatory approval" included. Standing on top of the vibrant technology like Spring, all in a "convention over configuration" design, makes Grails powerful and downright fun to use.

In sum, if you are maintaining a Grails 2 application, I encourage you to try moving it to Grails 3 to extend its longevity.

Topics

About the author

Chris Hermansen - Engaged in computing since graduating from the University of British Columbia in 1978, I have been a full-time Linux user since 2005 and a full-time Solaris, SunOS and UNIX System V user before that. On the technical side of things, I have spent a great deal of my career doing data analysis; especially spatial data analysis. I have a substantial amount of programming experience in relation to data analysis, using awk, Python, PostgreSQL, PostGIS and lately Groovy. I have also built a few...