Java & Ruby Development: Coding Cures, Concepts, API examples and more


Grails Demo Walkthrough

Developing a Grails Web Application

Tutorial will: Walk through steps of creating a robust Grails web application.
Application name: grails-mongodb-demo
The Goal: To track and record persons addresses.
The Source: https://github.com/keaplogik/grails-mongodb-demo

The application will use the Grails Framework to develop a web application using:
  1. MongoDB
  2. Spring Security
  3. Apache CXF (TODO)
  4. Jetty Web Server
  5. Maven build integration Note: Tutorial follows maven but maven has been removed from the git project.

-Index

Installing the Grails SDK And MongoDB

In this example, we are using Mac OS X. Some adjustments are required for Windows
Install Grails SDK
  1. Go to the Grails Download Page and install the latest 2.2.x binary
  2. Extract the file to any location, for example: ~/sdks
  3. Add the bin directory to your path
    • Linux/Unix: export GRAILS_HOME=~/sdks/grails-2.2.x
      • Then add the bin directory to you PATH variable: export PATH="$PATH:$GRAILS_HOME/bin
    • Windows: set an environment variable under My Computer/Advanced/Environment Variables
Executing Commands
  1. Open a console
    • The grails commands are used in the manner: grails [command]
    • You can also launch the grails script executor by simply typing grails.
Once you create a Grails project, you can use this console to quickly create domain classes, controllers, or many other Grails components. This will not be neccesary in this demo, since we will be using the IntelliJ Idea 11 IDE
Install Mongo
  1. Follow the quickstart guide online. There are specific instructions for each OS.

Project Setup

Project setup requires working within a terminal/command window
1. Creating a Grails Application
    $ cd ~/dev/workspaces
    $ grails create-app grails-mongodb-demo
2. Integrating with Maven
    $ cd grails-mongodb-demo
    $ grails create-pom com.mycompany
    $ mvn compile
The create-pom command expects a group id as an argument. The name and the version are taken from the application.properties of the application. The Maven plugin will keep the version in the pom.xml in sync with the version in application.properties.
Referenced From Grail Docs
3. Integrating with IntelliJ
**Integrating is simple!
    $ grails integrate-with --intellij
The last thing to do is open the project in IntelliJ, and thats it! Your ready to get started. A Run configuration should already be available.
Note: The latest version of this demo in git does not use maven. Maven really isn't the best first choice. To configure without maven, do not add a POM.xml to directory, and add the following definitions to the file at 'grails-app/conf/BuildConfig.groovy'

On with the maven tutorial..

Important Note: When grails creates the Maven POM, it adds a configuration to the plugin; grails-maven-plugin: <fork>true</fork>. If you want to be able to kill run processes from IntelliJ, comment out this line.
back to top

Configuring the Plugins

When working with grails we don't typically have dependency jars. Instead, Grails uses plugins for it's management of artifacts. These plugins aren't just artifacts, but also are an extension of the Grails framework. Plugins are available for download on their website, and more information on their specs can be found in the documentation. You may also install plugins with the grails command:
grails install-plugin [plugin-name]
Since we are working with maven, the plugins will be managed by maven. These plugins are available in the Grails remote repository.
This repo must be listed in your list of remote repositories:
<repository>
    <id>grails-plugins</id>
    <name>grails-plugins</name>
    <url>http://repo.grails.org/grails/plugins</url>
</repository>
Grails typically handles it's plugin management in a configuration file "BuildConfig.groovy". This file can be located at:
../my-app/grails-app/conf/BuildConfig.Groovy
To enable the grails command line to read the POMs, you must modify this file to use Maven when resolving dependencies. I would suggest removing the plugins property from this script as well.
grails.project.dependency.resolution = {
    
    //Use maven for dependency resolution
    pom true
    repositories {
        
        mavenLocal()    
    }

    //REMOVE if using maven 
    plugins {
        ...
    }
}
back to top

1. MongoDB Plugin

By default, Grails comes pre-packaged with Hibernate. Since we are working with a NoSql data store, we will need to remove that plugin from our maven dependency list.
Go into your pom.xml, and remove the dependency:
    <dependency>
        <groupId>org.grails.plugins</groupId>
        <artifactId>hibernate</artifactId>
        <version>2.1.1</version>
        <scope>runtime</scope>
        <type>zip</type>
    </dependency>
And replace it with:
    <dependency>
        <groupId>org.grails.plugins</groupId>
        <artifactId>mongodb</artifactId>
        <version>1.2.0</version>
        <scope>compile</scope>
        <type>zip</type>
    </dependency>
IMPORTANT! When replacing hibernate with MongoDB GORM, you need to also:
  1. Remove the database-migration dependencey from your POM.
  2. To ensure removal, run command:
    1.  grails uninstall-plugin database-migration
    2.  grails uninstall-plugin tomcat
  3. Refresh by running: grails refresh-dependencies
  4. There are some compatibility issues between some versions of the mongoDB plugin and Grails. If you get any errors running this application, first please consult the mongoDB plugin page.
Now your configured for MongoDB, and data will store in BSON type objects, as opposed to a relational store.
back to top

2. Jetty Plugin

Grails, by default comes with Apache Tomcat as it's web server. To change to Jetty, we need to remove the Tomcat plugin dependency from our pom.xml.
To make Jetty 7.6.0 the development time container for Grails, replace Tomcat plugin dependency with:
    <dependency>
        <groupId>org.grails.plugins</groupId>
        <artifactId>jetty</artifactId>
        <version>2.0.1</version>
        <scope>compile</scope>
        <type>zip</type>
    </dependency>
If your working in IntelliJ Idea, with auto-import turned on, IntelliJ will pull in the dependencies automatically.
back to top

3. Spring Security & Apache CXF Plugins

Add the following Maven dependencies to get Spring Security and Apache CXF support:
    <dependency>
        <groupId>org.grails.plugins</groupId>
        <artifactId>spring-security-core</artifactId>
        <version>1.2.7.3</version>
        <scope>compile</scope>
        <type>zip</type>
    </dependency>
    <dependency>
        <groupId>org.grails.plugins</groupId>
        <artifactId>webxml</artifactId>
        <version>1.4.1</version>
        <scope>compile</scope>
        <type>zip</type>
    </dependency>
The WSClient plugin is based on GroovyWS - which uses CXF
    <dependency>
        <groupId>org.grails.plugins</groupId>
        <artifactId>ws-client</artifactId>
        <version>1.0</version>
        <scope>compile</scope>
        <type>zip</type>
    </dependency>
Finally
  1. Stop the current run process in IntelliJ, or if in console: Ctrl-C
  2. Run: mvn clean compile (May not be necessary, but ensures a clean refresh)
  3. Click Run button in IntelliJ. If your not in IntelliJ, on the command line run: mvn grails:run-app
The application is now up and running on Jetty with MongoDB. It's amazing how simple and rapid getting started with Grails is!
back to top

Building an Application

When building out the application, we will be using a combination of grails on the command line, and IntelliJ. You can decide what works best for creating skeleton domain and controller classes. But for this tutorial, I'm trying not to be completely bound to IntelliJ IDE.

The Domain Model

* The initial goal of this application, is to create and update person address history *
To get started we are going to first build out some skeleton classes for our model.
  1. In IntelliJ, press command-alt-G or right click in project view -> Grails -> Run Target
    • If on command line, cd to your application directory
  2. On the command line type: create-domain-class org.mycompany.Person
    • In console prepend grails to the command.
  3. On the command line type: create-domain-class org.mycompany.Address
*Note: The names of database tables are automatically inferred from the name of the domain class.
Now we have skeleton classes for our two domain classes under: myapp/grails-app/domain
Open the Address class, and edit it as follows
class Address {

    String streetAddress;
    String city;
    String state;
    String zipCode;

    //Date moveInDate;
    //Date moveOutDate;

    static belongsTo = [person: Person]

    static constraints = {
        streetAddress(blank: false)
        city(blank: false)
        state(blank: false, size: 2..2)
        zipCode(blank: false, size: 5..5, validator: {val, obj -> val?.isNumber()})
        /*moveInDate(nullable: false, max:  new Date())
        moveOutDate(nullable: true, validator: { val, obj ->
            val?.after(obj.moveInDate)
        })*/
    }
}
  • The static field belongsTo indicates that the class Person assumes ownership of the relationship. i.e. An Address belongs to only one person (in this case).
  • The static field constraints, handles the validation logic for each field. Most fields cannot be blank, but the move out date can be blank if a person hasn't moved out yet. moveOutDate also has a custom validator to ensure the date is after the move in date. The dates are disabled to keep the demo simple. Feel free to uncomment them, to see how the scaffold view handles dates.
Open the Person class, and edit it as follows
class Person {

    String firstName;
    String lastName;

    static hasMany = [addresses: Address]

    static constraints = {
        firstName(blank: false)
        lastName(blank: false)
    }
}
  • The static field hasMany Defines a one-to-many association between two classes. i.e. a Person has many Addresses.
Important Note: We are using mongoDB, so using relationships with the hasMany definition will not be efficient in a production setting. The reason I chose to use this, is because scaffold views do not support lists within domain classes. The mongo GORM plugin does support this. So a better way to create Person addresses would look as follows:
class Person {

    String firstName;
    String lastName;
    List<Address> addresses; //We are going to store an array of addresses in the person collection.

    static constraints = {
        firstName(blank: false)
        lastName(blank: false)
    }
}
For the sake of simplicity, we will use the former case.
You can view descriptions on all domain class features on the Grails Quick Reference Page
back to top

Controllers with Grails Scaffolding

Grails by default, has a decent UI for testing your new MVC components. Basic CRUD operations can be performed with one line of code in the model's controller. These views (called scaffolding views) are generated by the compiler when calling grails run-app
To get started we are going to create controllers for our model classes.
  1. In IntelliJ, press command-alt-G or right click in project view -> Grails -> Run Target
    • If on command line, cd to your application directory
  2. On the command line type: create-controller org.mycompany.Person
    • In console prepend grails to the command.
  3. On the command line type: create-controller org.mycompany.Address
What this does is creates two controller classes named PersonController, and AddressController for our model.
Open up PersonController class.
  • remove the line def index() { } and replce it with def scaffold = Person
class PersonController {

    def scaffold = Person
}
Now do the same for the AddressController
class AddressController {

    def scaffold = Address
}

Configure GRAILS-Boootstrap

The Configuration class: Bootstrap is useful for, because you can use it to add data to the application when the application starts. Copy the below Bootstrap class. It will add many People, with many associated addresses to the mongoDB instance, next time you run the application.
import org.keaplogik.Person
import org.keaplogik.Address

class BootStrap {

    def init = { servletContext ->
        if (!Person.count()) {
            def johnDoe = new Person( firstName: "John", lastName: "Doe" ).save(failOnError: true)
            def joeReed = new Person( firstName: "Joe", lastName: "Reed" ).save(failOnError: true)
            def jimSmith = new Person( firstName: "Jim", lastName: "Smith" ).save(failOnError: true)
            def patrickHartwin = new Person( firstName: "Patrick", lastName: "Hartwin" ).save(failOnError: true)
            def steveGunther = new Person( firstName: "Steve", lastName: "Gunther" ).save(failOnError: true)
            def samWhiting = new Person( firstName: "Sam", lastName: "Whiting" ).save(failOnError: true)
            def sarahMathews = new Person( firstName: "Sarah", lastName: "Mathews" ).save(failOnError: true)
            def lisaPudock = new Person( firstName: "Lisa", lastName: "Pudock" ).save(failOnError: true)
            def karaWhiting = new Person( firstName: "Kara", lastName: "Whiting" ).save(failOnError: true)

            johnDoe.addToAddresses(
                    new Address(state: "NY", city: "Windsor", streetAddress: "117 W 2nd St", zipCode: "13865")
            ).addToAddresses(
                    new Address(state: "TX", city: "Alberta", streetAddress: "117 W 2nd St", zipCode: "55555")
            ).addToAddresses(
                    new Address(state: "NY", city: "Longely", streetAddress: "2 Sandy Creek", zipCode: "34009")
            ).addToAddresses(
                    new Address(state: "ME", city: "Ladly", streetAddress: "117 W 2nd St", zipCode: "55533")
            ).addToAddresses(
                    new Address(state: "KY", city: "Korba", streetAddress: "3 Apple St", zipCode: "40351")
            ).save(failOnError: true)

            joeReed.addToAddresses(
                    new Address(state: "KY", city: "Frankfort", streetAddress: "33 Main St", zipCode: "77625")
            ).addToAddresses(
                    new Address(state: "PA", city: "Scranton", streetAddress: "71 Kind Ave Apt 3", zipCode: "44567")
            ).addToAddresses(
                    new Address(state: "PA", city: "Scranton", streetAddress: "8559 Hard Rock", zipCode: "44567")
            ).addToAddresses(
                    new Address(state: "WV", city: "Charleston", streetAddress: "8233 Juniper Rd", zipCode: "33982")
            ).save(failOnError: true)

            jimSmith.addToAddresses(
                    new Address(state: "PA", city: "Blue Ridge", streetAddress: "780 Country Rd", zipCode: "44564")
            ).addToAddresses(
                    new Address(state: "TX", city: "Ft. Worth", streetAddress: "55 Holdem Dr." , zipCode: "77298")
            ).save(failOnError: true)

            patrickHartwin.addToAddresses(
                    new Address(state: "CA", city: "Sacramento", streetAddress: "1 Beach Rd", zipCode: "98765")
            ).addToAddresses(
                    new Address(state: "CA", city: "Sacramento", streetAddress: "53 Sinking Dr." , zipCode: "98765")
            ).save(failOnError: true)

            steveGunther.addToAddresses(
                    new Address(state: "CA", city: "Sacramento", streetAddress: "1 Beach Rd", zipCode: "98765")
            ).addToAddresses(
                    new Address(state: "CA", city: "Sacramento", streetAddress: "53 Sinking Dr." , zipCode: "98765")
            ).addToAddresses(
                    new Address(state: "CA", city: "Sacramento", streetAddress: "759 Sinking Dr." , zipCode: "98765")
            ).save(failOnError: true)

            samWhiting.addToAddresses(
                    new Address(state: "CA", city: "Sacramento", streetAddress: "1 Beach Rd", zipCode: "98765")
            ).save(failOnError: true)

            sarahMathews.addToAddresses(
                    new Address(state: "VT", city: "Burlington", streetAddress: "81 Lake Dr.", zipCode: "22183")
            ).addToAddresses(
                    new Address(state: "VT", city: "Burlington", streetAddress: "40 Shorten Ave Apt 33" , zipCode: "22183")
            ).addToAddresses(
                    new Address(state: "NY", city: "Plattsburgh", streetAddress: "1772 Lovely Lane" , zipCode: "22795")
            ).save(failOnError: true)

            lisaPudock.addToAddresses(
                    new Address(state: "VT", city: "Burlington", streetAddress: "81 Lake Dr.", zipCode: "22183")
            ).addToAddresses(
                    new Address(state: "VT", city: "Burlington", streetAddress: "40 Shorten Ave Apt 33" , zipCode: "22183")
            ).addToAddresses(
                    new Address(state: "NY", city: "Plattsburgh", streetAddress: "1772 Lovely Lane" , zipCode: "22795")
            ).save(failOnError: true)

            karaWhiting.addToAddresses(
                    new Address(state: "CA", city: "Sandiego", streetAddress: "9901 Shore Dr.", zipCode: "98741")
            ).save(failOnError: true)
        }
    }
    def destroy = {
    }
}
Finally, in IntelliJ click the run button. or from console mvn grails:run-app
Your application will be running on localhost. Play around with the default scaffolding UI and the validation.
back to top

Advanced Application Features

Enhanced UI Design

When building a user interface, it is important to first choose a style library. For this example we will be using bootstrap css and js to quickly enhance the user interface of our application. I also would like to be able to customize the default theme, so we will be adding the lesscss-resources plugin as well.

1. Configure Twitter Bootstrap

Head over to the Twitter Bootstrap Github Page and click on download the latest release in the Quick start section of the doc's.
  • Extract the files
  • Copy the bootstrap .js files into your project at web-app/js/bootstrap
  • Copy the bootstrap .less files into your project at web-app/less/bootstrap
  • Copy the /img/~.png files over to your projects web-app/images directory

2. Configure Bootstrap with Less compiler

First, add the new dependency lesscss-resources to your POM.
    <dependency>
        <groupId>org.grails.plugins</groupId>
        <artifactId>lesscss-resources</artifactId>
        <version>1.3.0.3</version>
        <scope>runtime</scope>
        <type>zip</type>
    </dependency>
In the web-app/less directory add a file custom-bootstrap.less. Place an import tag at the top of the page to import the bootstrap less file. Include an override of the iconSpritePath's to point to your images directory.
@import "bootstrap/bootstrap.less";

// Sprite icons path
// -------------------------
@iconSpritePath:          "../images/glyphicons-halflings.png";
@iconWhiteSpritePath:     "../images/glyphicons-halflings-white.png";
This file will be used to override bootstraps UI properties in the future. This way we can do some customization of the default UI theme.

Finally, open the grails Config class and add the following lines to the bottom of the page.
Note: After looking back, this code belongs in AplicationResources.groovy config file. Here is a link to the AplicationResources.groovy
grails.resources.modules = {

    bootstrap {
        resource url:'less/custom-bootstrap.less',attrs:[rel: "stylesheet/less", type:'css']
        dependsOn 'jquery'
    }

}
Earlier we imported the lesscss-resources plugin. This integrates with bootstrap and will compile the bootstrap.less files into css. Now, custom-bootstrap.less compiles on top of the provided bootstrap.less to allow overriding bootstrap variables used to generate stylings. The variables that can be overridden are found on the official github site.(https://github.com/twitter/bootstrap/blob/v2.0.2/less/variables.less)
To use bootstrap in any gsp page, add the tag <r:require modules="bootstrap"/> in the head.

3. Adding a custom controller

Now, we're going to modify our PersonController. Edit to look like so:
class PersonController {

    def scaffold = Person

    def index = {
        def people = Person.list([sort:"lastName", order:"asc"])

        return [people: people]
    }
}
Next we need to create our first Groovy Server Page. To do this we create a file at the location myapp/grails-app/views/person/index.gsp
Add the following code to the groovy server page.
<@ page contentType="text/html;charset=UTF-8" >
<@ page contentType="text/html;charset=UTF-8" >
<html xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html">
    <head>
        <meta name="layout" content="main"/>
        <r:require modules="bootstrap"/>
    </head>
    <body>
        <section>
            <div class="container">
                <div class="row">
                    <header class="page-header">
                        <h3>Person <small class="lead">Address List</small></h3>
                    </header>
                    <div class="span3">
                        <g:link class="btn btn-block btn-link" action="create">
                            Create New Person
                        </g:link>
                        <div class="well">
                            <ul class="nav nav-list">
                                <li class="nav-header">People</li>
                                <li class="active">
                                    <a id="view-all" href="#">
                                        <i class="icon-chevron-right pull-right"></i>
                                        <b>View All</b>
                                    </a>
                                </li>
                            <g:each in="${ people }" var="person" status="i">
                                <li>
                                    <a href="#Person-${person.id}">
                                        <i class="icon-chevron-right pull-right"></i>
                                        ${ "${ person.firstName } ${ person.lastName }" }
                                    </a>
                                </li>
                            </g:each>
                            </ul>
                        </div>
                    </div>
                    <div class="span9">
                    <g:each in="${ people }" var="person" status="i">
                        <div id="Person-${ person.id }" class="well well-small">
                            <table class="table table-bordered table-striped">
                                <caption>
                                    ${ "${ person.firstName } ${ person.lastName }" }: List of known addresses
                                </caption>
                                <thead>
                                    <tr>
                                        <th>State</th>
                                        <th>City</th>
                                        <th>Street</th>
                                        <th>Zip Code</th>
                                        <th>Options</th>
                                    </tr>
                                </thead>
                                <tbody>
                                <g:each in="${ person.addresses }" var="address">
                                    <tr>
                                        <td>${ address.state }</td>
                                        <td>${ address.city }</td>
                                        <td>${ address.streetAddress }</td>
                                        <td>${ address.zipCode }</td>
                                        <td><g:link class="btn btn-small btn-inverse" controller="address"
                                                    action="edit" id="${address.id}">
                                                <i class="icon-edit icon-white"></i>
                                            </g:link>
                                        </td>
                                    </tr>
                                </g:each>
                                </tbody>
                            </table>
                            <div class="btn-group">
                                <g:link class="btn btn-primary" action="edit" id="${person.id}">
                                    <i class="icon-edit icon-white"></i>Edit
                                </g:link>
                            </div>
                        </div>
                    </g:each>
                    </div>
                </div>
            </div>
        </section>
        <g:javascript>
            $('ul.nav > li > a').click(function(e){
                if($(this).attr('id') == "view-all"){
                    $('div[id*="Person-"]').fadeIn('fast');
                }else{
                    var aRef = $(this);
                    var tablesToHide = $('div[id*="Person-"]:visible').length > 1
                            ? $('div[id*="Person-"]:visible') : $($('.nav > li[class="active"] > a').attr('href'));

                    tablesToHide.hide();
                    $(aRef.attr('href')).fadeIn('fast');
                }
                $('.nav > li[class="active"]').removeClass('active');
                $(this).parent().addClass('active');
            });
        </g:javascript>
    </body>
</html>
***Note: We don't need to define any url mappings for the index. By default groovy will recognize that PersonController.index() maps to views/person/index.gsp.
Next, open the view: views/layouts/main.gsp. Remove any reference links to css files, except the main.css. Ensure that the tag <r:layoutResources /> is declared at both the bottom of the head and body tags. It should look similar to this.
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <title><g:layoutTitle default="Grails Mongo"/></title>
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="shortcut icon" href="${resource(dir: 'images', file: 'favicon.ico')}" type="image/x-icon">
        <link rel="apple-touch-icon" href="${resource(dir: 'images', file: 'apple-touch-icon.png')}">
        <link rel="apple-touch-icon" sizes="114x114" href="${resource(dir: 'images', file: 'apple-touch-icon-retina.png')}">
        <link rel="stylesheet" href="${resource(dir: 'css', file: 'main.css')}" type="text/css">
        <g:layoutHead/>
        <r:layoutResources />
    </head>
    <body>
        <g:layoutBody/>
        <div id="spinner" class="spinner" style="display:none;"><g:message code="spinner.alt" default="Loading&hellip;"/></div>
        <g:javascript library="application"/>
        <r:layoutResources />
    </body>
</html>

Also, edit the main.css file. Remove all css except for the section's commented /* PAGINATION */ and /* MESSAGES AND ERRORS */
Finally, go into the Configuration Class: URLMappings and modify as such:
class UrlMappings {

    static mappings = {
        "/$controller/$action?/$id?"{
            constraints {
                // apply constraints here
            }
        }

        "/"(controller: "person", action: "index")
        "500"(view:'/error')
    }
}
Note: The grails runtime supports hot swapping of gsp files except for the template files. It seems to work with URLMappings, but I haven't tested what classes are supported and aren't.

4. Customize Scaffold Templates

We are going to customize the default scaffolding template. This will allow us to keep using scaffolding for our basic CRUD operations.
In console run: grails install-template
Copies the the templates used by Grails during code generation to your project directory at src/templates
  1. Add the tag <r:require modules="bootstrap"/> below <meta name="layout" content="main"> in each of the views: show, list, create, edit
  2. Wrap each of the views with a <div class="container"></div> just inside the body
  3. Modify the nav classes. Wrap the Navigation list with a <div class="navbar"></div>
    • Change the div wrapping the list <div role="navigation"> to <div class="nav">
    • Add the class nav to the ul tag like so: <ul class="nav">
  4. Modify edit, show, create
    • Modify the tag: <fieldset class="buttons"> to <fieldset class="form-actions">
    • In show.gsp replace all class names "property-value" with "uneditable-input"
    • Change this span in show.gsp <span id="${p.name}-label"...> to a label tag.
  5. Modify list.gsp
    • The table class should look as follows: <table class="table table-bordered table-striped">
Feel free to play around with these views more. While, I'm leaving the instructions simple, it is likely you will want to clean up the template views a little more.

5. Improve Scaffold Output

One thing we would like to do is add proper error messages to CRUD forms. For each validation we can define an error message in the grails-app/i8n/messages.properties file. This file can map validation errors to their associated messages. Insert these lines at the bottom of this file.
person.firstName.blank=first name cannot be left blank
person.lastName.blank=last name cannot be left blank

address.streetAddress.blank=Street Address cannot be left blank
address.city.blank=City cannot be left blank
address.zipCode.blank=Zipcode cannot be left blank
address.zipCode.validator=Zipcode must be a valid number
address.zipCode.size=Zipcode must be a valid 5 digit number
address.state.blank=State cannot be left blank
address.state.size=State must be entered as a valid state code. Ex: NY
Next, we want to display the Person when displaying an Address, and display the Addresses when displaying a person. To do this, we override the toString method of each domain class.
So in Person
    String toString(){ return "${firstName} ${lastName}"}
And in Address
    String toString(){ return "${streetAddress} ${city}, ${state}, ${zipCode}"}
The application is now setup on bootstrap. Go ahead and run the application!
back to top

Adding Security

If you would like to follow along but haven't walked through each tutorial, downoad this tag on github. This is a snapshot of the demo project before adding security. To clone from git, use these commmand:
    $ git clone https://github.com/keaplogik/grails-mongodb-demo.git
    $ cd grails-mongodb-demo/
    $ git checkout v1.0

1. Configuring Spring Security Plugin

To start, ensure you have the spring-security-core plugin installed. If not, add it to your POM if using maven, otherwise add it to the dependency list of the BuildConfig class. If following along, with the grails-mongodb-demo, you will already have it installed.
A useful command s2-quickstart is built into the security plugin. It generates the controllers to handle authentication, as well the domain classes you'll need to store user information.
As in the springsource blog, we will create domain classes with names: SecUser and SecRole.
Also generated:
  • SecUserSecRole - links users to roles.
  • LoginController
  • LogoutController
  • Associated login/logout views
So start by runnning:
    $ grails s2-quickstart org.mycompany SecUser SecRole
The application now needs to specify mappings to the login/logout controllers. Add the following to the configuration class UrlMappings:
    "/login/$action?"(controller: "login")
    "/logout/$action?"(controller: "logout")
There are three ways you can configure security.
You can take a controller-centric approach and annotate the actions; work with static URL rules in Config.groovy; or define runtime rules in the database using request maps. Consult the springsource blog post Simplified Spring Security with Grails for more information.

2. Adding static URL maps

For the grails-mongodb-demo, we will secure the application using static URL rules.
The Rules:
  1. Any authenticated user has access to the Person Index
  2. Only Admin's can create/update/delete Persons or Addresses
To enable, set the securityConfigType and add interceptUrlMap Configurations to the Config.groovy class:
grails.plugins.springsecurity.securityConfigType = 'InterceptUrlMap'
grails.plugins.springsecurity.interceptUrlMap = [
        '/person/index':    ['ROLE_USER, ROLE_ADMIN, IS_AUTHENTICATED_FULLY'],
        '/person/**':       ['ROLE_ADMIN'],
        '/address/**':      ['ROLE_ADMIN'],
        '/js/**':           ['IS_AUTHENTICATED_ANONYMOUSLY'],
        '/css/**':          ['IS_AUTHENTICATED_ANONYMOUSLY'],
        '/images/**':       ['IS_AUTHENTICATED_ANONYMOUSLY'],
        '/*':               ['IS_AUTHENTICATED_FULLY'],
        '/login/**':        ['IS_AUTHENTICATED_ANONYMOUSLY'],
        '/logout/**':       ['IS_AUTHENTICATED_ANONYMOUSLY']

]
Optional security configurations:
grails.plugins.springsecurity.password.algorithm='SHA-512'      //pw encryption algorithm 
grails.plugins.springsecurity.portMapper.httpPort = "8080"      //port map for http
grails.plugins.springsecurity.portMapper.httpsPort = "8443"     //port map for https
grails.plugins.springsecurity.rejectIfNoRule = true             //force authentication if no rule exists

3. Bootstrap in security data

Our application needs to determine who has access to what URL. Because of this, we need to create a set of rules. They only need to be created once on application start, so lets use Bootstrap to create them.
class BootStrap {
    def init = {
        ...
        def userRole = SecRole.findByAuthority('ROLE_USER') ?: new SecRole(authority: 'ROLE_USER').save(failOnError: true)
        def adminRole = SecRole.findByAuthority('ROLE_ADMIN') ?: new SecRole(authority: 'ROLE_ADMIN').save(failOnError: true)
        ...
    }
}
Now configure two users; one with admin privilege and, one with default user privilege.
  • Admin User {u/p}: {admin/admin}
  • Default User {u/p}: {guest/guest}
In the Bootstrap.init{...}, add persistence for the default users :
    //add an admin and default user
    def adminUser = SecUser.findByUsername('admin') ?: new SecUser(
            username: 'admin',
            password: 'admin',
            enabled: true).save(failOnError: true)

    def basicUser = SecUser.findByUsername('guest') ?: new SecUser(
            username: 'guest',
            password: 'guest',                          //pw encoded by security plugin
            enabled: true).save(failOnError: true)

    if (!adminUser.authorities.contains(adminRole)) {
        SecUserSecRole.create adminUser, adminRole
    }
    if (!basicUser.authorities.contains(userRole)) {
        SecUserSecRole.create basicUser, userRole
    }

4. SecurityTagLib - Conditionally display gsp content

The security tags are part of the sec namespace, and can be used in your gsp's. Heres an example:
<sec:ifLoggedIn>
    <p>Your Logged in!</p>
</sec:ifLoggedIn>
We are going to use the security tag lib, to hide edit/create options on the person/index page. Specifically the condition will check if a user has the role: ROLE_ADMIN:
<sec:ifAllGranted roles="ROLE_ADMIN">hide this stuff</sec:ifAllGranted>
To view a list of tags and their explanations see the Grails SecurityTagLib Documentation.
Go ahead and open up the view grails-mongodb-demo/grails-app/views/person/index.gsp.
Wrap the Create New Person link like so:
<sec:ifAllGranted roles="ROLE_ADMIN">
<g:link class="btn btn-block btn-link" action="create">
    Create New Person
</g:link>
</sec:ifAllGranted>
And now the table data containing the options column, along with the button group:
<sec:ifAllGranted roles="ROLE_ADMIN">
<th>Options</th>
</sec:ifAllGranted>

...

<sec:ifAllGranted roles="ROLE_ADMIN">
<td><g:link class="btn btn-small btn-inverse" controller="address"
            action="edit" id="${address.id}">
        <i class="icon-edit icon-white"></i>
    </g:link>
</td>

...

<sec:ifAllGranted roles="ROLE_ADMIN">
<div class="btn-group">
    <g:link class="btn btn-primary" action="edit" id="${person.id}">
        <i class="icon-edit icon-white"></i>Edit
    </g:link>
</div>
</sec:ifAllGranted>
</sec:ifAllGranted>

5. Configure Security Pages with Bootstrap UI

We want to give the user an option to log out from any screen. Let's use a static navbar in our /layouts/main.gsp:
<body>
    <div class="navbar navbar-static-top">
        <div class="navbar-inner">
            <div class="container">
                <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </a>
                <g:link class="brand" uri="/">Address Tracker</g:link>
                <div class="nav-collapse">
                    <ul class="nav pull-right">
                        <sec:ifLoggedIn>
                        <li>
                            <g:link controller="logout" action="index">Logout</g:link>
                        </li>
                        </sec:ifLoggedIn>
                    </ul>
                </div>
            </div>
        </div>
    </div>
    <g:layoutBody/>
    ...
My auth.gsp page configured with twitter bootstrap is below, but you can modify yours however.
<html>
<head>
    <meta name='layout' content='main'/>
    <r:require modules="bootstrap"/>
    <title><g:message code="springSecurity.login.title"/></title>
</head>
<body>
    <div class='container'>
            <g:if test='${flash.message}'>
                <div class='alert alert-error'>${flash.message}</div>
            </g:if>
            <div class="row span12">
                <legend><g:message code="springSecurity.login.header"/></legend>
                <div class="span6">
                    <form action='${postUrl}' method='POST' id='loginForm' class='form-horizontal' autocomplete='off'>
                        <div class="control-group">
                            <label class="control-label" for='username'><g:message code="springSecurity.login.username.label"/>:</label>
                            <div class="controls">
                                <input type='text' class='text_' name='j_username' id='username'/>
                            </div>
                        </div>
                        <div class="control-group">
                            <label class="control-label" for='password'><g:message code="springSecurity.login.password.label"/>:</label>
                            <div class="controls">
                                <input type='password' class='text_' name='j_password' id='password'/>
                            </div>
                        </div>
                        <div class="control-group" id="remember_me_holder">
                            <div class="controls">
                                <label class="checkbox" for='remember_me'><g:message code="springSecurity.login.remember.me.label"/>
                                    <input type='checkbox' name='${rememberMeParameter}' id='remember_me'
                                           <g:if test='${hasCookie}'>checked='checked'</g:if>/>
                                </label>
                                <input class="btn btn-primary" type='submit' id="submit" value='${message(code: "springSecurity.login.button")}'/>
                            </div>
                        </div>
                    </form>
                </div>
                <div class="span4">
                    <div class="">
                        <dl class="dl-horizontal">
                            <dt>Admin User (u/p):</dt>
                            <dd>admin/admin</dd>
                        </dl>
                        <dl class="dl-horizontal">
                            <dt>Guest User (u/p):</dt>
                            <dd>guest/guest</dd>
                        </dl>
                    </div>
                </div>
            </div>
    </div>
</body>
</html>

And the denied.gsp:
<head>
    <meta name='layout' content='main' />
    <r:require modules="bootstrap"/>
    <title><g:message code="springSecurity.denied.title" /></title>
</head>

<body>
    <div class='container'>
        <div class='alert alert-block alert-error'>
            <g:message code="springSecurity.denied.message" />
            <br/><br/>
            <g:link class="btn btn-inverse" uri="/">Return Home</g:link>
        </div>
    </div>
</body>

The application is now setup with basic spring security. You can find a list of additional plugins with advanced configurations, on the spring security plugin page
back to top

Including Web Services